@geekbeer/minion 2.5.1 → 2.10.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/rules/minion.md CHANGED
@@ -3,6 +3,51 @@
3
3
  You are running on a Minion VPS managed by the @geekbeer/minion package.
4
4
  A local Agent API server runs at `http://localhost:3001` and a CLI tool `minion-cli` is available.
5
5
 
6
+ ## Architecture: Project / Workflow / Routine
7
+
8
+ ```
9
+ Project (組織・課金単位)
10
+ ├── Context (markdown, PMが更新)
11
+ ├── Members (minion + role: pm | engineer)
12
+ └── Workflows (何をするか + オプションのcronスケジュール)
13
+ ├── Versions (pipeline の不変スナップショット)
14
+ └── Executions (実行履歴, ステップごとの進捗)
15
+
16
+ Minion
17
+ └── Routines (いつ・何を, cron付き, ミニオンローカルのタスク)
18
+ ```
19
+
20
+ - **Workflow** はプロジェクトスコープ。バージョン管理されたパイプライン(スキル列)を持つ。オプションで `cron_expression` によるスケジュール実行が可能。HQ UIからワンショット実行もできる。
21
+ - **Routine** はミニオンスコープ。ミニオンローカルの定期タスク。cron_expression を持ち自律トリガーする。
22
+ - ミニオンは複数プロジェクトに `pm` または `engineer` として参加できる。
23
+ - ワークフローの各ステップには `assigned_role`(pm/engineer)と `requires_review` フラグがある。
24
+
25
+ ## Workflow Execution Model (DB ステートマシン)
26
+
27
+ ワークフロー実行はHQのDBがステートマシンとして管理する。ミニオン間通信は不要。
28
+
29
+ ```
30
+ 1. Execution 作成 (HQ UIのRunボタン or cronスケジュール)
31
+ → workflow_execution (status='pending')
32
+ → workflow_step_executions (全ステップ status='pending')
33
+
34
+ 2. ミニオンエージェントが GET /api/minion/pending-steps をポーリング
35
+ → 自分のロールに該当 & 前ステップ完了済みのステップを取得
36
+
37
+ 3. ミニオンがステップを実行し完了を報告
38
+ → POST /api/minion/execution で status/outcome を更新
39
+
40
+ 4. 次ステップが eligible に → 別のミニオンが検知して実行
41
+ → requires_review=true の場合はHQでレビュー承認後に次へ
42
+
43
+ 5. 全ステップ完了 → execution 全体を completed に
44
+ ```
45
+
46
+ **ポイント:**
47
+ - HQの責務は「executionレコードを作る(指示書発行)」だけ
48
+ - 各ミニオンは自分の担当ステップだけをポーリングして実行
49
+ - ミニオンエージェント(Port 3001)が軽量HTTPポーリングを行い、pending検知時のみClaude Codeを起動(トークン節約)
50
+
6
51
  ## CLI Commands
7
52
 
8
53
  ```bash
@@ -35,7 +80,7 @@ Authentication: `Authorization: Bearer $API_TOKEN` header (except where noted).
35
80
  | Method | Endpoint | Description |
36
81
  |--------|----------|-------------|
37
82
  | GET | `/api/list-skills` | List local deployed skills |
38
- | DELETE | `/api/skills/:name` | Delete local skill |
83
+ | DELETE | `/api/skills/:name` | Delete a local skill |
39
84
  | POST | `/api/skills/push/:name` | Push local skill to HQ |
40
85
  | POST | `/api/skills/fetch/:name` | Fetch skill from HQ and deploy locally |
41
86
  | GET | `/api/skills/remote` | List skills on HQ |
@@ -44,23 +89,22 @@ Authentication: `Authorization: Bearer $API_TOKEN` header (except where noted).
44
89
 
45
90
  | Method | Endpoint | Description |
46
91
  |--------|----------|-------------|
47
- | GET | `/api/workflows` | List all workflows with next_run |
48
- | POST | `/api/workflows` | Receive/upsert workflows from HQ |
92
+ | GET | `/api/workflows` | List all local workflows with next_run |
93
+ | POST | `/api/workflows` | Receive/upsert workflows (from HQ or local) |
49
94
  | POST | `/api/workflows/push/:name` | Push local workflow to HQ |
50
95
  | POST | `/api/workflows/fetch/:name` | Fetch workflow from HQ and deploy locally (+ pipeline skills) |
51
96
  | GET | `/api/workflows/remote` | List workflows on HQ |
52
- | PUT | `/api/workflows/:id/schedule` | Update workflow schedule (cron_expression, is_active) |
53
- | DELETE | `/api/workflows/:id` | Remove workflow |
97
+ | DELETE | `/api/workflows/:id` | Remove a local workflow |
54
98
  | POST | `/api/workflows/trigger` | Manual trigger. Body: `{workflow_id}` |
55
99
 
56
100
  ### Executions
57
101
 
58
102
  | Method | Endpoint | Description |
59
103
  |--------|----------|-------------|
60
- | GET | `/api/executions` | List execution history (?limit=50) |
104
+ | GET | `/api/executions` | List execution history (`?limit=50`, `?workflow_id=`) |
61
105
  | GET | `/api/executions/:id` | Get single execution |
62
- | GET | `/api/executions/:id/log` | Get execution log content |
63
- | POST | `/api/executions/:id/outcome` | Report outcome (no auth). Body: `{outcome}` |
106
+ | GET | `/api/executions/:id/log` | Get execution log content (`?tail=N`) |
107
+ | POST | `/api/executions/:id/outcome` | Report outcome (no auth). Body: `{outcome, summary?, details?}` |
64
108
 
65
109
  Outcome values: `success`, `failure`, `partial`
66
110
 
@@ -69,13 +113,25 @@ Outcome values: `success`, `failure`, `partial`
69
113
  | Method | Endpoint | Description |
70
114
  |--------|----------|-------------|
71
115
  | GET | `/api/terminal/sessions` | List tmux sessions |
72
- | POST | `/api/terminal/send` | Send keys. Body: `{session, keys}` |
73
- | POST | `/api/terminal/create` | Create session. Body: `{name, command?}` |
116
+ | POST | `/api/terminal/send` | Send keys. Body: `{session, input?, enter?, special?}` |
117
+ | POST | `/api/terminal/create` | Create session. Body: `{name?, command?}` |
74
118
  | POST | `/api/terminal/kill` | Kill session. Body: `{session}` |
75
- | GET | `/api/terminal/capture` | Capture pane content. Query: `?session=` |
119
+ | GET | `/api/terminal/capture` | Capture pane content. Query: `?session=&lines=100` |
76
120
  | GET | `/api/terminal/ttyd/status` | ttyd process status |
77
- | POST | `/api/terminal/ttyd/start` | Start ttyd. Body: `{session}` |
78
- | POST | `/api/terminal/ttyd/stop` | Stop ttyd. Body: `{session}` |
121
+ | POST | `/api/terminal/ttyd/start` | Start ttyd for session. Body: `{session}` |
122
+ | POST | `/api/terminal/ttyd/stop` | Stop ttyd for session. Body: `{session}` |
123
+ | POST | `/api/terminal/ttyd/stop-all` | Stop all ttyd processes |
124
+
125
+ ### Files
126
+
127
+ Files are stored in `~/files/`. Max upload size: 50MB.
128
+
129
+ | Method | Endpoint | Description |
130
+ |--------|----------|-------------|
131
+ | GET | `/api/files` | List files in directory. Query: `?path=subdir` |
132
+ | GET | `/api/files/*` | Download a file |
133
+ | POST | `/api/files/*` | Upload a file (Content-Type: `application/octet-stream`) |
134
+ | DELETE | `/api/files/*` | Delete a file or directory |
79
135
 
80
136
  ### Commands
81
137
 
@@ -86,6 +142,229 @@ Outcome values: `success`, `failure`, `partial`
86
142
 
87
143
  Available commands: `restart-agent`, `update-agent`, `restart-display`, `status-services`
88
144
 
145
+ ## HQ API Endpoints (https://<HQ_URL>)
146
+
147
+ Authentication: `Authorization: Bearer $API_TOKEN` header.
148
+ Base path: All endpoints are prefixed with `/api/minion` by the agent's API client.
149
+
150
+ ### Projects
151
+
152
+ | Method | Endpoint | Description |
153
+ |--------|----------|-------------|
154
+ | GET | `/api/minion/me/projects` | 参加プロジェクト一覧(role 含む) |
155
+
156
+ Response:
157
+ ```json
158
+ [
159
+ {
160
+ "id": "uuid",
161
+ "name": "project-name",
162
+ "description": "...",
163
+ "github_owner": "org",
164
+ "github_repo": "repo",
165
+ "role": "pm",
166
+ "created_at": "...",
167
+ "updated_at": "..."
168
+ }
169
+ ]
170
+ ```
171
+
172
+ `role` is either `"pm"` (project manager) or `"engineer"`.
173
+
174
+ ### Project Context
175
+
176
+ | Method | Endpoint | Description |
177
+ |--------|----------|-------------|
178
+ | GET | `/api/minion/me/project/[id]/context` | プロジェクトコンテキスト取得 |
179
+ | PUT | `/api/minion/me/project/[id]/context` | コンテキスト更新(PM のみ) |
180
+
181
+ PUT body: `{ "content": "markdown string" }`
182
+
183
+ ### Workflows (project-scoped, versioned)
184
+
185
+ | Method | Endpoint | Description |
186
+ |--------|----------|-------------|
187
+ | GET | `/api/minion/workflows` | 参加プロジェクトのアクティブなワークフロー一覧 |
188
+ | POST | `/api/minion/workflows` | ワークフローを push(新規作成 or 新バージョン) |
189
+
190
+ GET Response:
191
+ ```json
192
+ {
193
+ "workflows": [
194
+ {
195
+ "name": "daily-check",
196
+ "pipeline_skill_names": ["skill-1", "skill-2"],
197
+ "pipeline": [
198
+ {
199
+ "skill_version_id": "uuid",
200
+ "skill_name": "skill-1",
201
+ "skill_display_name": "Skill One",
202
+ "skill_version": 3,
203
+ "assigned_role": "engineer",
204
+ "requires_review": false,
205
+ "is_my_step": true
206
+ }
207
+ ],
208
+ "content": "...",
209
+ "version": 3,
210
+ "cron_expression": "0 9 * * *",
211
+ "project_id": "uuid",
212
+ "created_at": "..."
213
+ }
214
+ ]
215
+ }
216
+ ```
217
+
218
+ 各ステップの `is_my_step` はミニオン自身のプロジェクトロールと `assigned_role` の一致を示す。
219
+
220
+ POST body (push):
221
+ ```json
222
+ {
223
+ "name": "my-workflow",
224
+ "pipeline_skill_names": ["skill-1", "skill-2"],
225
+ "content": "Workflow description",
226
+ "project_id": "uuid",
227
+ "change_summary": "Added skill-2 to pipeline"
228
+ }
229
+ ```
230
+
231
+ push するとパイプライン内のスキル名が `skill_version_id` に解決され、新バージョンが自動作成される。
232
+
233
+ ### Pending Steps (軽量ポーリング)
234
+
235
+ | Method | Endpoint | Description |
236
+ |--------|----------|-------------|
237
+ | GET | `/api/minion/pending-steps` | 自分が実行すべき pending ステップ一覧 |
238
+
239
+ **このエンドポイントはミニオンエージェント(非AI)による高頻度ポーリング用。**
240
+ Claude Code(AIスキル実行)はステップ検知時のみ起動する。
241
+
242
+ Response:
243
+ ```json
244
+ {
245
+ "steps": [
246
+ {
247
+ "step_execution_id": "uuid",
248
+ "execution_id": "uuid",
249
+ "workflow_name": "daily-check",
250
+ "step_index": 0,
251
+ "skill_version_id": "uuid",
252
+ "assigned_role": "engineer"
253
+ }
254
+ ]
255
+ }
256
+ ```
257
+
258
+ 返却条件:
259
+ - `assigned_role` がミニオンのプロジェクトロールと一致
260
+ - ステップの `status` が `pending`
261
+ - 前ステップが全て `completed`(`requires_review` の場合は `approved` も必要)
262
+
263
+ ### Workflow Execution Recording
264
+
265
+ | Method | Endpoint | Description |
266
+ |--------|----------|-------------|
267
+ | POST | `/api/minion/workflow-execution` | 新規 execution 開始を記録 |
268
+ | POST | `/api/minion/execution` | ステップ実行状況を HQ に報告 |
269
+
270
+ POST `/api/minion/workflow-execution` body:
271
+ ```json
272
+ {
273
+ "workflow_version_id": "uuid"
274
+ }
275
+ ```
276
+
277
+ Response:
278
+ ```json
279
+ {
280
+ "execution_id": "uuid",
281
+ "status": "running",
282
+ "steps_count": 3
283
+ }
284
+ ```
285
+
286
+ POST `/api/minion/execution` body:
287
+ ```json
288
+ {
289
+ "workflow_execution_id": "uuid",
290
+ "steps": [
291
+ {
292
+ "step_index": 0,
293
+ "skill_version_id": "uuid",
294
+ "assigned_role": "engineer",
295
+ "status": "completed",
296
+ "outcome": "success",
297
+ "started_at": "...",
298
+ "completed_at": "..."
299
+ }
300
+ ],
301
+ "status": "running",
302
+ "started_at": "..."
303
+ }
304
+ ```
305
+
306
+ ### Issue Reporting (GitHub Issue 起票)
307
+
308
+ | Method | Endpoint | Description |
309
+ |--------|----------|-------------|
310
+ | POST | `/api/minion/report` | バグや改善提案を GitHub Issue として起票 |
311
+
312
+ POST `/api/minion/report` body:
313
+ ```json
314
+ {
315
+ "title": "HQ APIで502エラーが発生する",
316
+ "body": "## 状況\n...\n\n## 再現手順\n...\n\n## エラー情報\n...\n\n## 環境\n...",
317
+ "labels": ["bug", "critical"]
318
+ }
319
+ ```
320
+
321
+ Response:
322
+ ```json
323
+ {
324
+ "success": true,
325
+ "issue_url": "https://github.com/owner/repo/issues/42",
326
+ "issue_number": 42
327
+ }
328
+ ```
329
+
330
+ 使用可能なラベル: `bug`, `enhancement`, `critical`, `minor`
331
+
332
+ **使い方:** サービスのバグや改善点を発見したら、このエンドポイントで GitHub Issue を起票する。報告者のミニオン名と ID が自動で本文に付記される。
333
+
334
+ ### Routines (minion-scoped)
335
+
336
+ | Method | Endpoint | Description |
337
+ |--------|----------|-------------|
338
+ | GET | `/api/minion/routines` | 自分のルーティン一覧 |
339
+
340
+ Response:
341
+ ```json
342
+ [
343
+ {
344
+ "id": "uuid",
345
+ "name": "routine-name",
346
+ "pipeline_skill_names": ["skill-1", "execution-report"],
347
+ "content": "...",
348
+ "is_active": true,
349
+ "cron_expression": "0 9 * * 1-5",
350
+ "last_run": "...",
351
+ "next_run": "...",
352
+ "created_at": "...",
353
+ "updated_at": "..."
354
+ }
355
+ ]
356
+ ```
357
+
358
+ ### Skills
359
+
360
+ | Method | Endpoint | Description |
361
+ |--------|----------|-------------|
362
+ | GET | `/api/minion/skills` | HQ に登録されたスキル一覧 |
363
+ | GET | `/api/minion/skills/[name]` | スキル詳細取得(content, files 含む) |
364
+ | POST | `/api/minion/skills` | スキル登録/更新(新バージョン自動作成) |
365
+
366
+ スキルはバージョン管理される。push ごとに新バージョンが作成され、ファイルは Supabase Storage に保存される。
367
+
89
368
  ## Environment Variables
90
369
 
91
370
  | Variable | Description |
@@ -125,58 +404,52 @@ When not configured, the agent runs in standalone mode and HQ-dependent features
125
404
 
126
405
  ## Workflow Structure
127
406
 
128
- Workflows are stored locally in `workflows.json`. Each workflow object:
407
+ Workflows are project-scoped and versioned. Each version is an immutable snapshot of the pipeline.
408
+ Fetched from HQ via `GET /api/minion/workflows`. Also stored locally in `workflows.json`.
129
409
 
130
410
  ```json
131
411
  {
132
- "id": "uuid",
133
412
  "name": "my-workflow",
134
413
  "pipeline_skill_names": ["skill-1", "skill-2", "execution-report"],
414
+ "pipeline": [
415
+ {
416
+ "skill_version_id": "uuid",
417
+ "skill_name": "skill-1",
418
+ "skill_display_name": "Skill One",
419
+ "skill_version": 2,
420
+ "assigned_role": "engineer",
421
+ "requires_review": false,
422
+ "is_my_step": true
423
+ }
424
+ ],
135
425
  "content": "Markdown description of the workflow",
136
- "cron_expression": "0 9 * * 1-5",
137
- "is_active": true,
138
- "last_run": "2026-02-10T00:00:00.000Z"
426
+ "version": 2,
427
+ "cron_expression": "0 9 * * *",
428
+ "project_id": "uuid"
139
429
  }
140
430
  ```
141
431
 
142
432
  | Field | Type | Description |
143
433
  |-------|------|-------------|
144
- | `id` | string | UUID (auto-generated) |
145
434
  | `name` | string | Slug identifier (`/^[a-z0-9-]+$/`) |
146
- | `pipeline_skill_names` | string[] | Ordered skill names to execute |
435
+ | `pipeline_skill_names` | string[] | Ordered skill names (for display/push) |
436
+ | `pipeline` | PipelineStep[] | Resolved pipeline with version IDs and roles |
147
437
  | `content` | string | Markdown body describing the workflow |
148
- | `cron_expression` | string | Cron schedule (empty = manual only) |
149
- | `is_active` | boolean | Whether cron scheduling is enabled |
150
- | `last_run` | string\|null | ISO timestamp of last execution |
151
-
152
- ### Creating a workflow locally
438
+ | `version` | number | Current version number (auto-incremented on push) |
439
+ | `cron_expression` | string\|null | Cron schedule (null = manual/one-shot only) |
440
+ | `project_id` | string | UUID of the parent project |
153
441
 
154
- Use `POST /api/workflows` to create/update workflows:
155
-
156
- ```bash
157
- curl -X POST http://localhost:3001/api/workflows \
158
- -H "Authorization: Bearer $API_TOKEN" \
159
- -H "Content-Type: application/json" \
160
- -d '{
161
- "workflows": [{
162
- "id": "'$(uuidgen)'",
163
- "name": "my-workflow",
164
- "pipeline_skill_names": ["my-skill", "execution-report"],
165
- "content": "Description of what this workflow does",
166
- "cron_expression": "",
167
- "is_active": false
168
- }]
169
- }'
170
- ```
171
-
172
- Then activate with a schedule:
442
+ ### Pipeline Step Fields
173
443
 
174
- ```bash
175
- curl -X PUT http://localhost:3001/api/workflows/<id>/schedule \
176
- -H "Authorization: Bearer $API_TOKEN" \
177
- -H "Content-Type: application/json" \
178
- -d '{"cron_expression": "0 9 * * 1-5", "is_active": true}'
179
- ```
444
+ | Field | Type | Description |
445
+ |-------|------|-------------|
446
+ | `skill_version_id` | string | UUID of the specific skill version |
447
+ | `skill_name` | string | Skill slug name |
448
+ | `skill_display_name` | string | Human-readable skill name |
449
+ | `skill_version` | number | Skill version number |
450
+ | `assigned_role` | string | `"pm"` or `"engineer"` — who executes this step |
451
+ | `requires_review` | boolean | If true, human review required after completion |
452
+ | `is_my_step` | boolean | Whether this minion's role matches assigned_role |
180
453
 
181
454
  ### Syncing workflows with HQ
182
455
 
@@ -186,10 +459,73 @@ curl -X PUT http://localhost:3001/api/workflows/<id>/schedule \
186
459
 
187
460
  Pipeline skills must be deployed locally (in `~/.claude/skills/`) before pushing a workflow.
188
461
 
189
- ## Workflow Execution
462
+ ## Routine Structure
463
+
464
+ Routines are minion-scoped and fetched from HQ via `GET /api/minion/routines`.
465
+ They are also stored locally in `routines.json`. Each routine object:
466
+
467
+ ```json
468
+ {
469
+ "id": "uuid",
470
+ "name": "morning-work",
471
+ "pipeline_skill_names": ["project-workflow-check", "execution-report"],
472
+ "content": "Markdown description of the routine",
473
+ "cron_expression": "0 9 * * 1-5",
474
+ "is_active": true,
475
+ "last_run": "2026-02-10T09:00:00.000Z",
476
+ "next_run": "2026-02-11T09:00:00.000Z"
477
+ }
478
+ ```
479
+
480
+ | Field | Type | Description |
481
+ |-------|------|-------------|
482
+ | `id` | string | UUID (auto-generated) |
483
+ | `name` | string | Slug identifier (`/^[a-z0-9-]+$/`) |
484
+ | `pipeline_skill_names` | string[] | Ordered skill names to execute |
485
+ | `content` | string | Markdown body describing the routine |
486
+ | `cron_expression` | string | Cron schedule (empty = manual only) |
487
+ | `is_active` | boolean | Whether cron scheduling is enabled |
488
+ | `last_run` | string\|null | ISO timestamp of last execution |
489
+ | `next_run` | string\|null | ISO timestamp of next scheduled run |
190
490
 
191
- Workflows run on cron schedules. Each execution:
491
+ ## Routine Execution
492
+
493
+ Routines run on cron schedules. Each execution:
192
494
  1. Creates a tmux session
193
495
  2. Runs skills in pipeline order via `claude --dangerously-skip-permissions`
194
496
  3. Appends `execution-report` skill to report outcome
195
- 4. Environment variables `MINION_EXECUTION_ID`, `MINION_WORKFLOW_ID`, `MINION_WORKFLOW_NAME` are available during execution
497
+ 4. Environment variables `MINION_EXECUTION_ID`, `MINION_ROUTINE_ID`, `MINION_ROUTINE_NAME` are available during execution
498
+
499
+ ## Workflow Step Execution via Pending Steps
500
+
501
+ ミニオンエージェントは `GET /api/minion/pending-steps` を定期ポーリングし、
502
+ 自分の担当ステップを検知したら Claude Code を起動してスキルを実行する。
503
+
504
+ ```
505
+ ミニオンエージェント (Port 3001, 常駐, トークン不要)
506
+
507
+ ├── 軽量ポーリング: N秒ごとに GET /api/minion/pending-steps
508
+ │ → HTTPのみ、AIなし、コストゼロ
509
+ │ → pendingステップがなければ何もしない
510
+
511
+ └── ステップ検知時のみ:
512
+ 1. Claude Code 起動 → 該当スキルを実行
513
+ 2. POST /api/minion/execution → ステップ完了を報告
514
+ 3. Claude Code 終了 → トークン消費はここだけ
515
+ ```
516
+
517
+ ### Workflow Execution via Routine (従来方式)
518
+
519
+ ワークフローは `project-workflow-check` システムスキルを通じてルーティンからも実行可能:
520
+
521
+ ```
522
+ ルーティン: "morning-work" (cron: 0 9 * * 1-5)
523
+ pipeline: [project-workflow-check, execution-report]
524
+
525
+ project-workflow-check:
526
+ 1. GET /api/minion/me/projects → 参加プロジェクト一覧
527
+ 2. GET /api/minion/workflows → active なワークフロー一覧
528
+ 3. 各ワークフローの pipeline を順に実行
529
+
530
+ execution-report → HQ に結果報告
531
+ ```
package/server.js CHANGED
@@ -7,12 +7,13 @@
7
7
 
8
8
  const fs = require('fs')
9
9
  const path = require('path')
10
- const os = require('os')
11
10
 
12
11
  const fastify = require('fastify')({ logger: true })
13
12
  const { config, validate, isHqConfigured } = require('./config')
14
13
  const workflowRunner = require('./workflow-runner')
15
14
  const workflowStore = require('./workflow-store')
15
+ const routineRunner = require('./routine-runner')
16
+ const routineStore = require('./routine-store')
16
17
 
17
18
  const { registerRoutes, setOffline, getProcessManager, getAllowedCommands } = require('./routes')
18
19
  const { cleanupTtyd, killStaleTtydProcesses } = require('./routes/terminal')
@@ -31,8 +32,9 @@ async function shutdown(signal) {
31
32
 
32
33
  setOffline()
33
34
 
34
- // Stop workflow runner
35
+ // Stop workflow and routine runners
35
36
  workflowRunner.stopAll()
37
+ routineRunner.stopAll()
36
38
 
37
39
  // Stop terminal proxy and all ttyd processes
38
40
  stopTerminalProxy()
@@ -53,7 +55,7 @@ process.on('SIGINT', () => shutdown('SIGINT'))
53
55
  */
54
56
  function syncPermissions() {
55
57
  const bundledPath = path.join(__dirname, 'settings', 'permissions.json')
56
- const settingsDir = path.join(os.homedir(), '.claude')
58
+ const settingsDir = path.join(config.HOME_DIR, '.claude')
57
59
  const settingsPath = path.join(settingsDir, 'settings.json')
58
60
 
59
61
  try {
@@ -87,7 +89,7 @@ function syncPermissions() {
87
89
  */
88
90
  function syncTmuxConfig() {
89
91
  const bundledPath = path.join(__dirname, 'settings', 'tmux.conf')
90
- const destPath = path.join(os.homedir(), '.tmux.conf')
92
+ const destPath = path.join(config.HOME_DIR, '.tmux.conf')
91
93
 
92
94
  try {
93
95
  if (!fs.existsSync(bundledPath)) return
@@ -105,7 +107,7 @@ function syncTmuxConfig() {
105
107
  */
106
108
  function syncBundledRules() {
107
109
  const bundledRulesDir = path.join(__dirname, 'rules')
108
- const targetRulesDir = path.join(os.homedir(), '.claude', 'rules')
110
+ const targetRulesDir = path.join(config.HOME_DIR, '.claude', 'rules')
109
111
 
110
112
  try {
111
113
  if (!fs.existsSync(bundledRulesDir)) return
@@ -164,6 +166,17 @@ async function start() {
164
166
  console.error('[Server] Failed to load cached workflows:', err.message)
165
167
  }
166
168
 
169
+ // Load cached routines and start routine runner
170
+ try {
171
+ const cachedRoutines = await routineStore.load()
172
+ if (cachedRoutines.length > 0) {
173
+ console.log(`[Server] Loading ${cachedRoutines.length} cached routines`)
174
+ routineRunner.loadRoutines(cachedRoutines)
175
+ }
176
+ } catch (err) {
177
+ console.error('[Server] Failed to load cached routines:', err.message)
178
+ }
179
+
167
180
  if (isHqConfigured()) {
168
181
  console.log(`[Server] HQ URL: ${config.HQ_URL}`)
169
182
  } else {
@@ -4,6 +4,8 @@
4
4
  "Read",
5
5
  "Write",
6
6
  "Edit",
7
+ "WebSearch",
8
+ "WebFetch",
7
9
  "mcp__playwright__*"
8
10
  ],
9
11
  "deny": [
@@ -14,11 +14,11 @@ const { Cron } = require('croner')
14
14
  const { exec } = require('child_process')
15
15
  const { promisify } = require('util')
16
16
  const crypto = require('crypto')
17
- const os = require('os')
18
17
  const path = require('path')
19
18
  const fs = require('fs').promises
20
19
  const execAsync = promisify(exec)
21
20
 
21
+ const { config } = require('./config')
22
22
  const executionStore = require('./execution-store')
23
23
  const workflowStore = require('./workflow-store')
24
24
  const logManager = require('./lib/log-manager')
@@ -90,13 +90,17 @@ async function cleanupMarkerFile(sessionName) {
90
90
  * @param {object} workflow - Workflow configuration
91
91
  * @returns {Promise<{success: boolean, error?: string, sessionName?: string}>}
92
92
  */
93
- async function executeWorkflowSession(workflow, executionId, skillNames) {
94
- const homeDir = os.homedir()
93
+ async function executeWorkflowSession(workflow, executionId, skillNames, options = {}) {
94
+ const homeDir = config.HOME_DIR
95
95
  const sessionName = generateSessionName(workflow.id, executionId)
96
96
 
97
- // Build prompt: run each skill in sequence, then execution-report
97
+ // Build prompt: run each skill in sequence
98
+ // When skipExecutionReport is true (dispatched step), the minion server's
99
+ // post-execution hook handles completion reporting instead of /execution-report.
98
100
  const skillCommands = skillNames.map(name => `/${name}`).join(', then ')
99
- const prompt = `Run the following skills in order: ${skillCommands}. After completing all skills, run /execution-report to report the results.`
101
+ const prompt = options.skipExecutionReport
102
+ ? `Run the following skills in order: ${skillCommands}.`
103
+ : `Run the following skills in order: ${skillCommands}. After completing all skills, run /execution-report to report the results.`
100
104
 
101
105
  // Extend PATH to include common CLI installation locations
102
106
  const additionalPaths = [
@@ -187,11 +191,10 @@ async function executeWorkflowSession(workflow, executionId, skillNames) {
187
191
 
188
192
  while (Date.now() - startTime < timeout) {
189
193
  try {
190
- await execAsync(`tmux has-session -t "${sessionName}" 2>/dev/null`)
191
- await sleep(pollInterval)
194
+ await fs.access(exitCodeFile)
195
+ break // Exit code file exists — claude -p has finished
192
196
  } catch {
193
- // Session ended
194
- break
197
+ await sleep(pollInterval)
195
198
  }
196
199
  }
197
200
 
@@ -245,7 +248,7 @@ async function saveExecution(executionData) {
245
248
  * @param {object} workflow - Workflow configuration
246
249
  * @returns {Promise<{execution_id: string, session_name: string}>}
247
250
  */
248
- async function runWorkflow(workflow) {
251
+ async function runWorkflow(workflow, options = {}) {
249
252
  const pipelineSkillNames = workflow.pipeline_skill_names || []
250
253
 
251
254
  if (pipelineSkillNames.length === 0) {
@@ -285,7 +288,7 @@ async function runWorkflow(workflow) {
285
288
  })
286
289
 
287
290
  // Execute all skills in one session
288
- const result = await executeWorkflowSession(workflow, executionId, pipelineSkillNames)
291
+ const result = await executeWorkflowSession(workflow, executionId, pipelineSkillNames, options)
289
292
 
290
293
  const completedAt = new Date().toISOString()
291
294
  console.log(`[WorkflowRunner] executeWorkflowSession returned: success=${result.success}, error=${result.error || 'none'}`)