@geekbeer/minion 3.52.0 → 3.53.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.
@@ -4,6 +4,64 @@
4
4
 
5
5
  ---
6
6
 
7
+ ## Webページの読み取り・要約 🧪 (experimental, v3.53.0〜)
8
+
9
+ ユーザーから「このURLを要約して」「このページの情報を抽出して」「このページの一覧を取ってきて」と依頼された場合の手順。
10
+
11
+ ### まずローカルAPIを試す
12
+
13
+ ```bash
14
+ curl -X POST http://localhost:8080/api/web/extract \
15
+ -H "Authorization: Bearer $API_TOKEN" -H "Content-Type: application/json" \
16
+ -d '{"url": "対象URL", "hint": "何を抽出したいか短く (任意)"}' | jq
17
+ ```
18
+
19
+ 返ってきた JSON の `title` `content` `structured` を使って要約・回答を作成する。`structured` には初回アクセス時に LLM が選んだフィールドが入る。
20
+
21
+ このAPIは内部で Playwright + Readability を回して **メインセッションには結果 JSON だけ返す** ため、Playwright MCP を使うときに起きていたチャットコンテキストのトークン肥大化が回避できる。
22
+
23
+ ### Playwright MCP を使うべき場面
24
+
25
+ `/api/web/extract` で対応できないのは以下のケース。このときだけ `mcp__playwright__*` を使う:
26
+
27
+ - ログイン必須ページ (Cookie/2FA 等の認証必要)
28
+ - フォーム入力・複数ページ遷移を伴う操作
29
+ - ボタンクリック→動的に追加されるコンテンツの取得
30
+ - Lancers コンペ応募など、明らかに対話的操作が必要なフロー
31
+
32
+ **単純な閲覧・抽出用途では MCP を使わない。**
33
+
34
+ ### よくあるパターン
35
+
36
+ | ユーザー依頼 | 推奨手段 |
37
+ |--------------|----------|
38
+ | 「このQiita記事を要約」 | `/api/web/extract` |
39
+ | 「Lancersコンペの一覧を取得」 | `/api/web/extract` |
40
+ | 「このプロダクトページから価格を抽出」 | `/api/web/extract` |
41
+ | 「ログインしてダッシュボード操作」 | Playwright MCP |
42
+ | 「フォームを送信」 | Playwright MCP |
43
+ | 「複数ページ巡回して全件取得」 | `/api/web/extract` をループ呼び出し (各ページに対して) |
44
+
45
+ ### キャッシュの確認・破棄 (debug)
46
+
47
+ ```bash
48
+ # キャッシュ済みレシピ一覧
49
+ curl -H "Authorization: Bearer $API_TOKEN" http://localhost:8080/api/web/recipes | jq
50
+
51
+ # 特定のレシピを削除 (壊れたセレクタを強制再生成させたい場合)
52
+ curl -X DELETE -H "Authorization: Bearer $API_TOKEN" \
53
+ "http://localhost:8080/api/web/recipes?template=lancers.jp/work/proposal/:id&fingerprint=abc123def456"
54
+ ```
55
+
56
+ ### 失敗時の対処
57
+
58
+ - `503 PLAYWRIGHT_UNAVAILABLE` → ホスト側で `npx playwright install chromium` を実行 (sudo 不要)
59
+ - `503 LLM_UNAVAILABLE` → primary LLM 未設定。`PUT /api/llm/config -d '{"primary":"claude"}'` で primary を指定するか、fallback として `PUT /api/secrets/ANTHROPIC_API_KEY` で API キーを投入
60
+ - `502 PRIMARY_LLM_BAD_JSON` → primary LLM が JSON 形式で返さなかった。プラグインの認証状態を `GET /api/llm/plugins` で確認し、必要なら別 plugin を primary にする
61
+ - `500 extract timeout` → ページが重すぎる/JSレンダリング待ちが長すぎる。Playwright MCP に切り替えて手動操作
62
+
63
+ ---
64
+
7
65
  ## スキルの修正
8
66
 
9
67
  ### 1. ローカルのスキルを編集する
@@ -362,6 +362,29 @@ async function buildContextPrefix(message, context, sessionId, workspaceId, refe
362
362
  )
363
363
  }
364
364
 
365
+ // Web page extraction guidance (experimental, v3.53.0)
366
+ if (!sessionId) {
367
+ const port = require('../../core/config').config.AGENT_PORT
368
+ parts.push(
369
+ '[Webページ読み取りについて 🧪 experimental]',
370
+ 'Webページの読み取り・要約・情報抽出が必要なときは、まず以下のローカルAPIを試すこと:',
371
+ '',
372
+ '```bash',
373
+ `curl -X POST http://localhost:${port}/api/web/extract \\`,
374
+ ' -H "Authorization: Bearer $API_TOKEN" -H "Content-Type: application/json" \\',
375
+ ' -d \'{"url": "対象URL", "hint": "抽出したい内容を短く"}\'',
376
+ '```',
377
+ '',
378
+ 'このAPIは内部で Playwright + Readability を回し、抽出済みJSONだけを返すため、',
379
+ 'DOM全体がチャットに流れ込んでトークン肥大化することを防げる。',
380
+ '初回アクセスで学習したセレクタはSQLiteにキャッシュされ、2回目以降はLLM呼び出しなしで抽出される。',
381
+ '',
382
+ 'Playwright MCP (`mcp__playwright__*`) は **ログイン・フォーム入力・複数画面の対話操作**が必要な場合のみ使用する。',
383
+ '単純な閲覧・要約・一覧取得用途ではMCPを使わない。',
384
+ ''
385
+ )
386
+ }
387
+
365
388
  // File output guidance — always inject on new sessions
366
389
  if (!sessionId) {
367
390
  parts.push(
@@ -770,6 +793,11 @@ function streamViaLegacyLlmCommand(res, prompt, sessionId, workspaceId, original
770
793
  child.on('close', async (code) => {
771
794
  activeChatChild = null
772
795
 
796
+ console.log(`[Chat] child closed: code=${code}, response=${fullResponse.length}chars, turns=${turnCount}, stderr=${stderrBuffer.length}bytes, session=${resolvedSessionId}`)
797
+ if (stderrBuffer.trim()) {
798
+ console.log(`[Chat] final stderr (tail 500): ${stderrBuffer.slice(-500)}`)
799
+ }
800
+
773
801
  // Store messages in chat-store
774
802
  if (resolvedSessionId) {
775
803
  // If this was a new session, also store the user message now
@@ -782,11 +810,15 @@ function streamViaLegacyLlmCommand(res, prompt, sessionId, workspaceId, original
782
810
  }
783
811
  }
784
812
 
785
- // If exit code is non-zero and no response was generated, send error
786
- if (code !== 0 && !fullResponse) {
813
+ if (code !== 0) {
787
814
  const errorMsg = stderrBuffer.trim() || `Claude CLI exited with code ${code}`
788
- console.error(`[Chat] CLI failed (exit ${code}): ${errorMsg}`)
789
- const errorEvent = JSON.stringify({ type: 'error', error: errorMsg })
815
+ console.error(`[Chat] CLI failed (exit ${code}, partial=${!!fullResponse}): ${errorMsg}`)
816
+ const errorEvent = JSON.stringify({
817
+ type: 'error',
818
+ error: errorMsg,
819
+ partial: !!fullResponse,
820
+ exit_code: code,
821
+ })
790
822
  res.write(`data: ${errorEvent}\n\n`)
791
823
  }
792
824
 
package/linux/server.js CHANGED
@@ -85,6 +85,7 @@ const { todoRoutes } = require('../core/routes/todos')
85
85
  const { emailRoutes } = require('../core/routes/emails')
86
86
  const { daemonRoutes } = require('../core/routes/daemons')
87
87
  const { llmRoutes } = require('../core/routes/llm')
88
+ const { webRoutes } = require('../core/routes/web')
88
89
 
89
90
  // Linux-specific routes
90
91
  const { commandRoutes, getProcessManager, getAllowedCommands } = require('./routes/commands')
@@ -298,6 +299,7 @@ async function registerAllRoutes(app) {
298
299
  await app.register(emailRoutes)
299
300
  await app.register(daemonRoutes, { heartbeatStatus: () => ({ running: !!heartbeatTimer, last_beat_at: lastBeatAt }) })
300
301
  await app.register(llmRoutes)
302
+ await app.register(webRoutes)
301
303
 
302
304
  // Linux-specific routes
303
305
  await app.register(commandRoutes)
package/mac/server.js CHANGED
@@ -72,6 +72,7 @@ const { todoRoutes } = require('../core/routes/todos')
72
72
  const { emailRoutes } = require('../core/routes/emails')
73
73
  const { daemonRoutes } = require('../core/routes/daemons')
74
74
  const { llmRoutes } = require('../core/routes/llm')
75
+ const { webRoutes } = require('../core/routes/web')
75
76
 
76
77
  // macOS-specific routes
77
78
  const { commandRoutes, getProcessManager, getAllowedCommands } = require('./routes/commands')
@@ -284,6 +285,7 @@ async function registerAllRoutes(app) {
284
285
  await app.register(emailRoutes)
285
286
  await app.register(daemonRoutes, { heartbeatStatus: () => ({ running: !!heartbeatTimer, last_beat_at: lastBeatAt }) })
286
287
  await app.register(llmRoutes)
288
+ await app.register(webRoutes)
287
289
 
288
290
  // macOS-specific routes
289
291
  await app.register(commandRoutes)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@geekbeer/minion",
3
- "version": "3.52.0",
3
+ "version": "3.53.1",
4
4
  "description": "AI Agent runtime for Minion - manages status and skill deployment on VPS",
5
5
  "main": "linux/server.js",
6
6
  "bin": {
@@ -33,13 +33,17 @@
33
33
  "db:migration:new": "node scripts/new-migration.js"
34
34
  },
35
35
  "dependencies": {
36
+ "@mozilla/readability": "^0.5.0",
36
37
  "croner": "^9.0.0",
37
38
  "fastify": "^5.2.2",
39
+ "linkedom": "^0.18.0",
40
+ "turndown": "^7.2.0",
38
41
  "ws": "^8.0.0"
39
42
  },
40
43
  "optionalDependencies": {
41
44
  "better-sqlite3": "^11.0.0",
42
- "node-pty": "^1.0.0"
45
+ "node-pty": "^1.0.0",
46
+ "playwright": "^1.48.0"
43
47
  },
44
48
  "engines": {
45
49
  "node": ">=22.0.0"
package/rules/core.md CHANGED
@@ -103,7 +103,27 @@ minion-cli --version # バージョン確認
103
103
 
104
104
  `http://localhost:8080` — 認証: `Authorization: Bearer $API_TOKEN`
105
105
 
106
- 主なカテゴリ: Health, Skills, Workflows, Executions, Terminal, Files, Commands, Permissions, Admin (HQ-pushed freeze)
106
+ 主なカテゴリ: Health, Skills, Workflows, Executions, Terminal, Files, Commands, Permissions, Admin (HQ-pushed freeze), Web Extraction (experimental)
107
+
108
+ #### Web Page Extraction 🧪 (experimental, v3.53.0〜)
109
+
110
+ Webページの読み取り・要約・情報抽出には、**Playwright MCP より先に** ローカルAPI `POST /api/web/extract` を試すこと。
111
+
112
+ このAPIは内部でヘッドレスブラウザ→本文クリーン (Readability) →セレクタ抽出 (LLMサブプロセスで自動学習) →JSON 返却までを完結させる。**メインセッションに巨大な DOM が流れ込まないため、トークン肥大化やセッション終了を防げる。**
113
+
114
+ ```bash
115
+ curl -X POST http://localhost:8080/api/web/extract \
116
+ -H "Authorization: Bearer $API_TOKEN" -H "Content-Type: application/json" \
117
+ -d '{"url": "https://example.com/article/123", "hint": "本文と著者を抽出"}'
118
+ ```
119
+
120
+ レシピは初回アクセス時に LLM (Haiku) で生成・SQLite (`page_recipes` テーブル) に保存され、2回目以降の構造的に同じページでは LLM 呼び出しなしで抽出される。
121
+
122
+ Playwright MCP (`mcp__playwright__*`) は **フォーム入力・クリック・複数画面遷移など対話的な操作**が必要な場合のみ使用すること。単に「ページを読む」目的では MCP を使わない。
123
+
124
+ **実験的機能**: レスポンス形状は予告なく変わる可能性がある。要件: (1) primary LLM 設定済み (`PUT /api/llm/config` で `claude` 等を選択、`hq llm primary <name>` でも可) または `ANTHROPIC_API_KEY` シークレット設定済み、(2) ホスト上で `npx playwright install chromium` 実行済み。primary LLM が設定されていれば API キー不要 (Claude Code CLI の認証情報を再利用)。
125
+
126
+ 詳細仕様は `~/.minion/docs/api-reference.md` の「Web Page Extraction」セクション、ユースケースは `~/.minion/docs/task-guides.md` の「Webページの読み取り・要約」を参照。
107
127
 
108
128
  #### Billing-Driven Freeze (v3.52.0〜)
109
129
 
@@ -423,6 +423,29 @@ async function buildContextPrefix(message, context, sessionId, workspaceId, refe
423
423
  )
424
424
  }
425
425
 
426
+ // Web page extraction guidance (experimental, v3.53.0)
427
+ if (!sessionId) {
428
+ const port = require('../../core/config').config.AGENT_PORT
429
+ parts.push(
430
+ '[Webページ読み取りについて 🧪 experimental]',
431
+ 'Webページの読み取り・要約・情報抽出が必要なときは、まず以下のローカルAPIを試すこと:',
432
+ '',
433
+ '```bash',
434
+ `curl -X POST http://localhost:${port}/api/web/extract \\`,
435
+ ' -H "Authorization: Bearer $API_TOKEN" -H "Content-Type: application/json" \\',
436
+ ' -d \'{"url": "対象URL", "hint": "抽出したい内容を短く"}\'',
437
+ '```',
438
+ '',
439
+ 'このAPIは内部で Playwright + Readability を回し、抽出済みJSONだけを返すため、',
440
+ 'DOM全体がチャットに流れ込んでトークン肥大化することを防げる。',
441
+ '初回アクセスで学習したセレクタはSQLiteにキャッシュされ、2回目以降はLLM呼び出しなしで抽出される。',
442
+ '',
443
+ 'Playwright MCP (`mcp__playwright__*`) は **ログイン・フォーム入力・複数画面の対話操作**が必要な場合のみ使用する。',
444
+ '単純な閲覧・要約・一覧取得用途ではMCPを使わない。',
445
+ ''
446
+ )
447
+ }
448
+
426
449
  // File output guidance — always inject on new sessions
427
450
  if (!sessionId) {
428
451
  parts.push(
@@ -790,6 +813,12 @@ function streamViaLegacyLlmCommand(res, prompt, sessionId, workspaceId, original
790
813
 
791
814
  child.on('close', async (code) => {
792
815
  activeChatChild = null
816
+
817
+ console.log(`[Chat] child closed: code=${code}, response=${fullResponse.length}chars, turns=${turnCount}, stderr=${stderrBuffer.length}bytes, session=${resolvedSessionId}`)
818
+ if (stderrBuffer.trim()) {
819
+ console.log(`[Chat] final stderr (tail 500): ${stderrBuffer.slice(-500)}`)
820
+ }
821
+
793
822
  if (resolvedSessionId) {
794
823
  if (!sessionId) {
795
824
  await chatStore.addMessage(resolvedSessionId, { role: 'user', content: originalMessage || prompt }, undefined, workspaceId)
@@ -798,9 +827,15 @@ function streamViaLegacyLlmCommand(res, prompt, sessionId, workspaceId, original
798
827
  await chatStore.addMessage(resolvedSessionId, { role: 'assistant', content: fullResponse }, turnCount, workspaceId)
799
828
  }
800
829
  }
801
- if (code !== 0 && !fullResponse) {
830
+ if (code !== 0) {
802
831
  const errorMsg = stderrBuffer.trim() || `Claude CLI exited with code ${code}`
803
- res.write(`data: ${JSON.stringify({ type: 'error', error: errorMsg })}\n\n`)
832
+ console.error(`[Chat] CLI failed (exit ${code}, partial=${!!fullResponse}): ${errorMsg}`)
833
+ res.write(`data: ${JSON.stringify({
834
+ type: 'error',
835
+ error: errorMsg,
836
+ partial: !!fullResponse,
837
+ exit_code: code,
838
+ })}\n\n`)
804
839
  }
805
840
 
806
841
  const session = await chatStore.load(workspaceId)
package/win/server.js CHANGED
@@ -67,6 +67,7 @@ const { todoRoutes } = require('../core/routes/todos')
67
67
  const { emailRoutes } = require('../core/routes/emails')
68
68
  const { daemonRoutes } = require('../core/routes/daemons')
69
69
  const { llmRoutes } = require('../core/routes/llm')
70
+ const { webRoutes } = require('../core/routes/web')
70
71
 
71
72
  // Validate configuration
72
73
  validate()
@@ -232,6 +233,7 @@ async function registerRoutes(app) {
232
233
  await app.register(emailRoutes)
233
234
  await app.register(daemonRoutes, { heartbeatStatus: () => ({ running: !!heartbeatTimer, last_beat_at: lastBeatAt }) })
234
235
  await app.register(llmRoutes)
236
+ await app.register(webRoutes)
235
237
 
236
238
  // Shutdown endpoint — allows detached restart/update scripts to trigger
237
239
  // graceful shutdown (offline heartbeat) before force-killing the process.