@geekbeer/minion 3.25.0 → 3.27.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.
@@ -125,6 +125,7 @@ async function executeSkillNode(node) {
125
125
  assigned_role,
126
126
  input_data,
127
127
  revision_feedback,
128
+ review_history,
128
129
  } = node
129
130
 
130
131
  console.log(
@@ -187,6 +188,9 @@ async function executeSkillNode(node) {
187
188
  if (revision_feedback) {
188
189
  runPayload.revision_feedback = revision_feedback
189
190
  }
191
+ if (review_history && review_history.length > 0) {
192
+ runPayload.review_history = review_history
193
+ }
190
194
 
191
195
  const runUrl = `http://localhost:${config.AGENT_PORT || 8080}/api/skills/run`
192
196
  const runResp = await fetch(runUrl, {
@@ -342,6 +342,7 @@ async function skillRoutes(fastify, opts) {
342
342
  workflow_name,
343
343
  role,
344
344
  revision_feedback,
345
+ review_history,
345
346
  dag_node_execution_id,
346
347
  } = request.body || {}
347
348
 
@@ -391,6 +392,7 @@ async function skillRoutes(fastify, opts) {
391
392
  const runOptions = {}
392
393
  if (role) runOptions.role = role
393
394
  if (revision_feedback) runOptions.revisionFeedback = revision_feedback
395
+ if (review_history && review_history.length > 0) runOptions.reviewHistory = review_history
394
396
 
395
397
  // Run asynchronously — respond immediately
396
398
  const executionPromise = (async () => {
@@ -117,6 +117,7 @@ curl http://localhost:8080/api/terminal/wsl/status \
117
117
  ### Files
118
118
 
119
119
  Files are stored in `~/files/`. Max upload size: 50MB.
120
+ **バイナリ成果物(PDF、画像、ZIP等)はここに配置する。** ユーザーがHQダッシュボードからダウンロードできる。
120
121
 
121
122
  | Method | Endpoint | Description |
122
123
  |--------|----------|-------------|
@@ -1448,3 +1449,55 @@ Response:
1448
1449
 
1449
1450
  ローカルエージェントの `/api/project-memories` は上記 HQ API へのプロキシ。
1450
1451
  詳細なリクエスト/レスポンス仕様はローカル API セクションの「Project Memories」を参照。
1452
+
1453
+ ### Notes (HQ, プロジェクトノート)
1454
+
1455
+ プロジェクトに紐づくノート。**テキストベースの成果物(レポート、調査結果、要約等)はノートに保存すること。**
1456
+ ノートはHQダッシュボードでユーザーが即座に閲覧・編集・検索でき、バージョン管理もされる。
1457
+
1458
+ | Method | Endpoint | Description |
1459
+ |--------|----------|-------------|
1460
+ | GET | `/api/minion/projects/:projectId/notes` | プロジェクトのノート一覧。Query: `?status=active&include_content=true` |
1461
+ | POST | `/api/minion/projects/:projectId/notes` | ノートを作成しプロジェクトにリンク |
1462
+ | GET | `/api/minion/projects/:projectId/notes/:noteId` | ノート詳細(全文含む) |
1463
+ | PATCH | `/api/minion/projects/:projectId/notes/:noteId` | ノートを更新(内容変更時にバージョン自動作成) |
1464
+ | GET | `/api/minion/projects/:projectId/notes/search?q=keyword` | ノートを全文検索 |
1465
+
1466
+ POST `/api/minion/projects/:projectId/notes` body:
1467
+ ```json
1468
+ {
1469
+ "title": "調査レポート: ユーザー行動分析",
1470
+ "content": "## 概要\n..."
1471
+ }
1472
+ ```
1473
+
1474
+ | Field | Type | Required | Description |
1475
+ |-------|------|----------|-------------|
1476
+ | `title` | string | Yes | ノートのタイトル |
1477
+ | `content` | string | No | ノートの本文(Markdown) |
1478
+
1479
+ PATCH `/api/minion/projects/:projectId/notes/:noteId` body:
1480
+ ```json
1481
+ {
1482
+ "title": "更新されたタイトル",
1483
+ "content": "更新された本文",
1484
+ "change_summary": "データを追加"
1485
+ }
1486
+ ```
1487
+
1488
+ | Field | Type | Required | Description |
1489
+ |-------|------|----------|-------------|
1490
+ | `title` | string | No | 新しいタイトル |
1491
+ | `content` | string | No | 新しい本文 |
1492
+ | `change_summary` | string | No | 変更の要約(バージョン履歴に記録) |
1493
+
1494
+ #### hq CLI ラッパー
1495
+
1496
+ ```bash
1497
+ hq note create <project_id> --title "タイトル" --content "本文"
1498
+ hq note create <project_id> --title "タイトル" --file /tmp/report.md
1499
+ hq note update <project_id> <note_id> --content "更新内容" --change-summary "修正理由"
1500
+ hq note list <project_id>
1501
+ hq note get <project_id> <note_id>
1502
+ hq note search <project_id> "キーワード"
1503
+ ```
package/linux/bin/hq CHANGED
@@ -17,6 +17,13 @@
17
17
  # hq fetch dag-workflow <id> - Get DAG workflow details (graph, version)
18
18
  # hq fetch dag-execution <id> - Get DAG execution details (nodes, status)
19
19
  # hq list workspaces - List workspaces this minion belongs to
20
+ # hq note create <project_id> --title "Title" --content "Body" [--file path]
21
+ # - Create a note linked to the project
22
+ # hq note update <project_id> <note_id> --content "Body" [--title "Title"] [--file path] [--change-summary "summary"]
23
+ # - Update an existing note
24
+ # hq note list <project_id> - List notes in the project
25
+ # hq note get <project_id> <note_id> - Get a single note with full content
26
+ # hq note search <project_id> <query> - Search notes in the project
20
27
  # hq create dag-workflow <body.json> - Create a DAG workflow (PM only). Body: {project_id, name, graph?, ...}
21
28
  # hq put dag-workflow <id> <body.json> - Update DAG workflow draft (PM only). Body: {graph?, content?, ...}
22
29
  # hq publish dag-workflow <id> - Publish the draft as a new version (PM only, validated)
@@ -146,6 +153,11 @@ print_usage() {
146
153
  echo " hq fetch dag-workflow <id> - Get DAG workflow details" >&2
147
154
  echo " hq fetch dag-execution <id> - Get DAG execution details" >&2
148
155
  echo " hq list workspaces - List workspaces this minion belongs to" >&2
156
+ echo " hq note create <project_id> --title \"Title\" --content \"Body\" [--file path]" >&2
157
+ echo " hq note update <project_id> <note_id> --content \"Body\" [--title \"Title\"] [--file path]" >&2
158
+ echo " hq note list <project_id> - List notes in the project" >&2
159
+ echo " hq note get <project_id> <note_id> - Get a single note" >&2
160
+ echo " hq note search <project_id> <query> - Search notes in the project" >&2
149
161
  echo " hq create dag-workflow <body.json> - Create a DAG workflow (PM only)" >&2
150
162
  echo " hq put dag-workflow <id> <body.json> - Update DAG workflow draft (PM only)" >&2
151
163
  echo " hq publish dag-workflow <id> - Publish the draft as a new version (PM only)" >&2
@@ -270,6 +282,168 @@ case "${1:-}" in
270
282
  esac
271
283
  ;;
272
284
 
285
+ note)
286
+ action="${2:-}"
287
+ case "$action" in
288
+ create)
289
+ project_id="${3:-}"
290
+ if [ -z "$project_id" ]; then
291
+ echo "Usage: hq note create <project_id> --title \"Title\" --content \"Body\" [--file path]" >&2
292
+ exit 1
293
+ fi
294
+ shift 3
295
+ note_title=""
296
+ note_content=""
297
+ note_file=""
298
+ while [ $# -gt 0 ]; do
299
+ case "$1" in
300
+ --title) note_title="$2"; shift 2 ;;
301
+ --content) note_content="$2"; shift 2 ;;
302
+ --file) note_file="$2"; shift 2 ;;
303
+ *) echo "Unknown option: $1" >&2; exit 1 ;;
304
+ esac
305
+ done
306
+ if [ -z "$note_title" ]; then
307
+ echo "Error: --title is required" >&2
308
+ exit 1
309
+ fi
310
+ if [ -n "$note_file" ]; then
311
+ if [ ! -f "$note_file" ]; then
312
+ echo "Error: file not found: $note_file" >&2
313
+ exit 1
314
+ fi
315
+ note_content=$(cat "$note_file")
316
+ fi
317
+ if [ -z "$note_content" ]; then
318
+ echo "Error: --content or --file is required" >&2
319
+ exit 1
320
+ fi
321
+ # Build JSON body using jq or python3
322
+ if command -v jq &>/dev/null; then
323
+ body=$(jq -n --arg t "$note_title" --arg c "$note_content" '{title: $t, content: $c}')
324
+ elif command -v python3 &>/dev/null; then
325
+ body=$(python3 -c "import json,sys; print(json.dumps({'title': sys.argv[1], 'content': sys.argv[2]}))" "$note_title" "$note_content")
326
+ else
327
+ echo "Error: jq or python3 is required to build JSON" >&2
328
+ exit 1
329
+ fi
330
+ tmpfile=$(mktemp)
331
+ echo "$body" > "$tmpfile"
332
+ send_json_request POST "$BASE_URL/projects/$project_id/notes" "$tmpfile"
333
+ rm -f "$tmpfile"
334
+ ;;
335
+ update)
336
+ project_id="${3:-}"
337
+ note_id="${4:-}"
338
+ if [ -z "$project_id" ] || [ -z "$note_id" ]; then
339
+ echo "Usage: hq note update <project_id> <note_id> --content \"Body\" [--title \"Title\"] [--file path] [--change-summary \"summary\"]" >&2
340
+ exit 1
341
+ fi
342
+ shift 4
343
+ note_title=""
344
+ note_content=""
345
+ note_file=""
346
+ change_summary=""
347
+ while [ $# -gt 0 ]; do
348
+ case "$1" in
349
+ --title) note_title="$2"; shift 2 ;;
350
+ --content) note_content="$2"; shift 2 ;;
351
+ --file) note_file="$2"; shift 2 ;;
352
+ --change-summary) change_summary="$2"; shift 2 ;;
353
+ *) echo "Unknown option: $1" >&2; exit 1 ;;
354
+ esac
355
+ done
356
+ if [ -n "$note_file" ]; then
357
+ if [ ! -f "$note_file" ]; then
358
+ echo "Error: file not found: $note_file" >&2
359
+ exit 1
360
+ fi
361
+ note_content=$(cat "$note_file")
362
+ fi
363
+ if [ -z "$note_content" ] && [ -z "$note_title" ]; then
364
+ echo "Error: at least --content, --file, or --title is required" >&2
365
+ exit 1
366
+ fi
367
+ # Build JSON body
368
+ if command -v jq &>/dev/null; then
369
+ body="{}"
370
+ [ -n "$note_title" ] && body=$(echo "$body" | jq --arg t "$note_title" '. + {title: $t}')
371
+ [ -n "$note_content" ] && body=$(echo "$body" | jq --arg c "$note_content" '. + {content: $c}')
372
+ [ -n "$change_summary" ] && body=$(echo "$body" | jq --arg s "$change_summary" '. + {change_summary: $s}')
373
+ elif command -v python3 &>/dev/null; then
374
+ body=$(python3 -c "
375
+ import json, sys
376
+ d = {}
377
+ if sys.argv[1]: d['title'] = sys.argv[1]
378
+ if sys.argv[2]: d['content'] = sys.argv[2]
379
+ if sys.argv[3]: d['change_summary'] = sys.argv[3]
380
+ print(json.dumps(d))
381
+ " "$note_title" "$note_content" "$change_summary")
382
+ else
383
+ echo "Error: jq or python3 is required to build JSON" >&2
384
+ exit 1
385
+ fi
386
+ tmpfile=$(mktemp)
387
+ echo "$body" > "$tmpfile"
388
+ # Use PATCH for update
389
+ response=$(curl -s -w "\n%{http_code}" -X PATCH \
390
+ -H "Authorization: Bearer $API_TOKEN" \
391
+ -H "Content-Type: application/json" \
392
+ --data-binary "@$tmpfile" \
393
+ "$BASE_URL/projects/$project_id/notes/$note_id")
394
+ rm -f "$tmpfile"
395
+ http_code=$(echo "$response" | tail -1)
396
+ body_out=$(echo "$response" | sed '$d')
397
+ if [ "$http_code" -ge 200 ] && [ "$http_code" -lt 300 ]; then
398
+ echo "$body_out" | format_json
399
+ else
400
+ echo "Error: HQ API returned HTTP $http_code" >&2
401
+ echo "$body_out" >&2
402
+ exit 1
403
+ fi
404
+ ;;
405
+ list)
406
+ project_id="${3:-}"
407
+ if [ -z "$project_id" ]; then
408
+ echo "Usage: hq note list <project_id>" >&2
409
+ exit 1
410
+ fi
411
+ fetch_resource "$BASE_URL/projects/$project_id/notes?include_content=false"
412
+ ;;
413
+ get)
414
+ project_id="${3:-}"
415
+ note_id="${4:-}"
416
+ if [ -z "$project_id" ] || [ -z "$note_id" ]; then
417
+ echo "Usage: hq note get <project_id> <note_id>" >&2
418
+ exit 1
419
+ fi
420
+ fetch_resource "$BASE_URL/projects/$project_id/notes/$note_id"
421
+ ;;
422
+ search)
423
+ project_id="${3:-}"
424
+ query="${4:-}"
425
+ if [ -z "$project_id" ] || [ -z "$query" ]; then
426
+ echo "Usage: hq note search <project_id> <query>" >&2
427
+ exit 1
428
+ fi
429
+ # URL-encode the query
430
+ if command -v jq &>/dev/null; then
431
+ encoded=$(printf '%s' "$query" | jq -sRr @uri)
432
+ elif command -v python3 &>/dev/null; then
433
+ encoded=$(python3 -c "import urllib.parse,sys; print(urllib.parse.quote(sys.argv[1]))" "$query")
434
+ else
435
+ encoded="$query"
436
+ fi
437
+ fetch_resource "$BASE_URL/projects/$project_id/notes/search?q=$encoded"
438
+ ;;
439
+ *)
440
+ echo "Unknown note action: $action" >&2
441
+ echo "Usage: hq note {create|update|list|get|search} ..." >&2
442
+ exit 1
443
+ ;;
444
+ esac
445
+ ;;
446
+
273
447
  *)
274
448
  print_usage
275
449
  exit 1
@@ -337,6 +337,22 @@ async function buildContextPrefix(message, context, sessionId, workspaceId) {
337
337
  )
338
338
  }
339
339
 
340
+ // File output guidance — always inject on new sessions
341
+ if (!sessionId) {
342
+ parts.push(
343
+ '[ファイル出力ルール]',
344
+ '成果物の保存先は以下のルールに従うこと。`/home/minion/` 直下にファイルを保存しないこと。',
345
+ '',
346
+ '- **テキスト成果物**(レポート、調査結果、要約等)→ ノートに保存: `hq note create <project_id> --title "タイトル" --content "本文"`',
347
+ '- **バイナリファイル**(PDF、画像、ZIP等)→ `~/files/` に配置(ユーザーがHQからダウンロード可能)',
348
+ '- **一時ファイル** → `/tmp/` に配置(作業後に削除)',
349
+ '',
350
+ 'プロジェクトコンテキストがない場合、テキスト成果物はローカルメモリ(`POST /api/memory`)に保存する。',
351
+ '詳細は `~/.minion/rules/core.md` の「File Output Rules」セクションを参照。',
352
+ ''
353
+ )
354
+ }
355
+
340
356
  if (context) {
341
357
  switch (context.type) {
342
358
  case 'skill':
@@ -77,10 +77,20 @@ async function executeWorkflowSession(workflow, executionId, skillNames, options
77
77
  ? `You are acting as the ${options.role} role in this session. Read ~/.minion/roles/${options.role}.md for your role guidelines before proceeding.\n\n`
78
78
  : ''
79
79
 
80
- // Inject revision feedback if this is a re-execution after reviewer requested changes
81
- const revisionContext = options.revisionFeedback
82
- ? `## Revision Feedback\nThe reviewer requested changes to your previous output. Address the following feedback:\n${options.revisionFeedback}\n\n`
83
- : ''
80
+ // Inject revision feedback and review history if this is a re-execution after reviewer requested changes
81
+ let revisionContext = ''
82
+ if (options.reviewHistory && options.reviewHistory.length > 0) {
83
+ const historyLines = options.reviewHistory.map((entry, i) => {
84
+ const label = entry.status === 'revision_requested' ? 'Revision Requested'
85
+ : entry.status === 'approved' ? 'Approved'
86
+ : entry.status === 'rejected' ? 'Rejected'
87
+ : entry.status
88
+ return `${i + 1}. **${label}** (${entry.reviewed_at})${entry.comment ? `\n ${entry.comment}` : ''}`
89
+ }).join('\n')
90
+ revisionContext = `## Review History\nThis task has been reviewed ${options.reviewHistory.length} time(s). Address all feedback:\n${historyLines}\n\n`
91
+ } else if (options.revisionFeedback) {
92
+ revisionContext = `## Revision Feedback\nThe reviewer requested changes to your previous output. Address the following feedback:\n${options.revisionFeedback}\n\n`
93
+ }
84
94
 
85
95
  const prompt = `${rolePrefix}${revisionContext}Run the following skills in order: ${skillCommands}.`
86
96
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@geekbeer/minion",
3
- "version": "3.25.0",
3
+ "version": "3.27.0",
4
4
  "description": "AI Agent runtime for Minion - manages status and skill deployment on VPS",
5
5
  "main": "linux/server.js",
6
6
  "bin": {
package/rules/core.md CHANGED
@@ -307,6 +307,62 @@ API詳細は `~/.minion/docs/api-reference.md` の「Todos」セクションを
307
307
  - **ブロッカーが解決したらスレッドを `resolve` する。** 放置しない。
308
308
  - **解決策をプロジェクトメモリーに保存する。** 同じブロッカーに再度遭遇する他のミニオンの助けになる。
309
309
 
310
+ ## File Output Rules (ファイル出力ルール)
311
+
312
+ 成果物の保存先は以下のルールに従うこと。**`/home/minion/` 直下にファイルを保存してはならない。** ユーザーがHQダッシュボードからアクセスできない場所にファイルを置いても意味がない。
313
+
314
+ ### 保存先の判断基準
315
+
316
+ | 出力タイプ | 保存先 | 理由 |
317
+ |-----------|--------|------|
318
+ | テキスト(レポート、調査結果、要約、分析結果等) | **ノート** (`hq note create`) | HQダッシュボードで即閲覧・編集・検索可能。バージョン管理もされる |
319
+ | バイナリ(PDF、画像、ZIP、Excel等) | **`~/files/`** | Files API経由でユーザーがダウンロード可能 |
320
+ | コード・設定ファイル | **ワークスペース内の適切なディレクトリ** | プロジェクトのリポジトリやワークスペースに配置 |
321
+ | 一時ファイル(中間データ、一時スクリプト等) | **`/tmp/`** | ユーザーに見せる必要なし。作業後に削除 |
322
+
323
+ ### ノートへの保存 (`hq note` コマンド)
324
+
325
+ テキストベースの成果物は原則ノートに保存する。ノートはプロジェクトに紐づき、HQダッシュボードで閲覧できる。
326
+
327
+ ```bash
328
+ # ノートを作成(プロジェクトに紐づけ)
329
+ hq note create <project_id> --title "レポートタイトル" --content "本文..."
330
+
331
+ # 長い内容はファイルから読み込む
332
+ hq note create <project_id> --title "調査結果" --file /tmp/report.md
333
+
334
+ # 既存ノートを更新
335
+ hq note update <project_id> <note_id> --content "更新された内容"
336
+
337
+ # ノート一覧を確認
338
+ hq note list <project_id>
339
+
340
+ # ノート内容を取得
341
+ hq note get <project_id> <note_id>
342
+
343
+ # ノートを検索
344
+ hq note search <project_id> "キーワード"
345
+ ```
346
+
347
+ ### `~/files/` への保存
348
+
349
+ バイナリファイルやユーザーがダウンロードする必要があるファイルは `~/files/` に配置する。
350
+
351
+ ```bash
352
+ # ファイルを保存(~/files/ 配下に自動配置)
353
+ cp /tmp/output.pdf ~/files/output.pdf
354
+
355
+ # サブディレクトリで整理も可能
356
+ mkdir -p ~/files/reports/2026-04
357
+ cp /tmp/report.pdf ~/files/reports/2026-04/report.pdf
358
+ ```
359
+
360
+ ### プロジェクトコンテキストがない場合
361
+
362
+ チャットやルーティンなど、プロジェクトに紐づかない作業の場合:
363
+ - テキスト成果物 → ローカルメモリ (`POST /api/memory`) に保存
364
+ - バイナリファイル → `~/files/` に保存
365
+
310
366
  ## Documentation
311
367
 
312
368
  API仕様やタスク手順の詳細は以下を参照:
@@ -400,6 +400,22 @@ async function buildContextPrefix(message, context, sessionId, workspaceId) {
400
400
  )
401
401
  }
402
402
 
403
+ // File output guidance — always inject on new sessions
404
+ if (!sessionId) {
405
+ parts.push(
406
+ '[ファイル出力ルール]',
407
+ '成果物の保存先は以下のルールに従うこと。`/home/minion/` 直下にファイルを保存しないこと。',
408
+ '',
409
+ '- **テキスト成果物**(レポート、調査結果、要約等)→ ノートに保存: `hq note create <project_id> --title "タイトル" --content "本文"`',
410
+ '- **バイナリファイル**(PDF、画像、ZIP等)→ `~/files/` に配置(ユーザーがHQからダウンロード可能)',
411
+ '- **一時ファイル** → `/tmp/` に配置(作業後に削除)',
412
+ '',
413
+ 'プロジェクトコンテキストがない場合、テキスト成果物はローカルメモリ(`POST /api/memory`)に保存する。',
414
+ '詳細は `~/.minion/rules/core.md` の「File Output Rules」セクションを参照。',
415
+ ''
416
+ )
417
+ }
418
+
403
419
  if (context) {
404
420
  switch (context.type) {
405
421
  case 'skill':
@@ -85,9 +85,19 @@ async function executeWorkflowSession(workflow, executionId, skillNames, options
85
85
  ? `You are acting as the ${options.role} role in this session. Read ~/.minion/roles/${options.role}.md for your role guidelines before proceeding.\n\n`
86
86
  : ''
87
87
 
88
- const revisionContext = options.revisionFeedback
89
- ? `## Revision Feedback\nThe reviewer requested changes to your previous output. Address the following feedback:\n${options.revisionFeedback}\n\n`
90
- : ''
88
+ let revisionContext = ''
89
+ if (options.reviewHistory && options.reviewHistory.length > 0) {
90
+ const historyLines = options.reviewHistory.map((entry, i) => {
91
+ const label = entry.status === 'revision_requested' ? 'Revision Requested'
92
+ : entry.status === 'approved' ? 'Approved'
93
+ : entry.status === 'rejected' ? 'Rejected'
94
+ : entry.status
95
+ return `${i + 1}. **${label}** (${entry.reviewed_at})${entry.comment ? `\n ${entry.comment}` : ''}`
96
+ }).join('\n')
97
+ revisionContext = `## Review History\nThis task has been reviewed ${options.reviewHistory.length} time(s). Address all feedback:\n${historyLines}\n\n`
98
+ } else if (options.revisionFeedback) {
99
+ revisionContext = `## Revision Feedback\nThe reviewer requested changes to your previous output. Address the following feedback:\n${options.revisionFeedback}\n\n`
100
+ }
91
101
 
92
102
  const prompt = `${rolePrefix}${revisionContext}Run the following skills in order: ${skillCommands}.`
93
103