@geekbeer/minion 3.60.1 → 3.60.5

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.
@@ -2084,9 +2084,9 @@ POST レスポンス例:
2084
2084
 
2085
2085
  | Method | Endpoint | Description |
2086
2086
  |--------|----------|-------------|
2087
- | POST | `/api/minion/workspaces/:id/drive/decks/:deckId/slides` | スライド追加。Body: `{position?, background?, speaker_notes?}` (`position` 省略時は末尾) |
2087
+ | POST | `/api/minion/workspaces/:id/drive/decks/:deckId/slides` | スライド追加。Body: `{position?, background?, speaker_notes?}` (`position` 省略時は末尾)。`background` の形式は下記参照 |
2088
2088
  | GET | `/api/minion/workspaces/:id/drive/slides/:slideId` | スライド + 全要素 (z-order 昇順) |
2089
- | PATCH | `/api/minion/workspaces/:id/drive/slides/:slideId` | スライド更新。Body: `{version, background?, speaker_notes?, order_key?, is_skipped?}`。`version` 必須。`is_skipped=true` でプレゼン時に飛ばされる(順序は保持) |
2089
+ | PATCH | `/api/minion/workspaces/:id/drive/slides/:slideId` | スライド更新。Body: `{version, background?, speaker_notes?, order_key?, is_skipped?}`。`version` 必須。`background` の形式は下記参照。`is_skipped=true` でプレゼン時に飛ばされる(順序は保持) |
2090
2090
  | DELETE | `/api/minion/workspaces/:id/drive/slides/:slideId` | スライド削除 (要素は CASCADE) |
2091
2091
 
2092
2092
  #### 要素操作 (text / image)
@@ -2097,6 +2097,23 @@ POST レスポンス例:
2097
2097
  | PATCH | `/api/minion/workspaces/:id/drive/elements/:elementId` | 要素更新。Body: `{version, x?, y?, width?, height?, rotation?, data?, order_key?}`。`version` 必須、`element_type` は不変 |
2098
2098
  | DELETE | `/api/minion/workspaces/:id/drive/elements/:elementId` | 要素削除 |
2099
2099
 
2100
+ #### background ペイロード形式
2101
+
2102
+ `slides.background` および POST/PATCH の `background` フィールドは **オブジェクト必須** (文字列 `"#fff"` 等は 400 で弾かれる)。形式は以下の2種類のみ:
2103
+
2104
+ ```json
2105
+ { "type": "color", "value": "#ffffff" }
2106
+ ```
2107
+
2108
+ ```json
2109
+ { "type": "image", "src": "deck:<uuid>.<ext>" }
2110
+ ```
2111
+
2112
+ - `type` は `"color"` または `"image"` のいずれか必須
2113
+ - `color` の場合 `value` (任意の CSS color 文字列、通常は `#rrggbb`) が必須
2114
+ - `image` の場合 `src` (非空文字列) が必須。形式は下記「image data の src」と同じルール
2115
+ - 400 `invalid_background` が返るのは、オブジェクトでない / `type` が不明 / 必須フィールド欠落のいずれか
2116
+
2100
2117
  #### data ペイロード形式
2101
2118
 
2102
2119
  **text:**
@@ -2115,12 +2132,63 @@ POST レスポンス例:
2115
2132
  }
2116
2133
  ```
2117
2134
 
2135
+ `style` 内の **全フィールドは必須** (省略すると 400)。値の制約:
2136
+
2137
+ | フィールド | 型 | 許容値 |
2138
+ |-----------|----|-------|
2139
+ | `fontFamily` | string | 任意の CSS font-family 文字列 (空文字不可) |
2140
+ | `fontSize` | number | `> 0` |
2141
+ | `fontWeight` | number | `400` または `700` のみ |
2142
+ | `fontStyle` | string | `"normal"` または `"italic"` のみ |
2143
+ | `color` | string | 任意の CSS color 文字列 |
2144
+ | `textAlign` | string | `"left"` / `"center"` / `"right"` のみ |
2145
+ | `lineHeight` | number | `> 0` |
2146
+
2147
+ `content` は HTML を含めて良い。サーバ側 (`src/lib/workspace/slide-html.ts`) で allowlist サニタイズを通り、許可外のタグはテキストとして escape され、許可外の属性・スタイルプロパティは破棄される。
2148
+
2149
+ **許可タグ一覧:**
2150
+
2151
+ | タグ | 用途 |
2152
+ |------|------|
2153
+ | `<p>` | 段落 (Tiptap が改行ごとに自動付与) |
2154
+ | `<br>` | 強制改行 (void タグ) |
2155
+ | `<strong>` | 太字 |
2156
+ | `<em>` | 斜体 |
2157
+ | `<u>` | 下線 |
2158
+ | `<s>` | 打ち消し線 |
2159
+ | `<ul>` `<ol>` `<li>` | 箇条書き / 番号付きリスト |
2160
+ | `<span style="...">` | インライン書式 (下記スタイル限定) |
2161
+
2162
+ **許可属性:** `span` の `style` のみ。`class` `id` `data-*` `href` 等は全て破棄される。
2163
+
2164
+ **許可スタイルプロパティ:**
2165
+
2166
+ | プロパティ | 許容値 |
2167
+ |-----------|-------|
2168
+ | `color` | `#rgb` / `#rrggbb` / `#rrggbbaa` (3〜8桁の hex)、`rgb(...)` / `rgba(...)`、`transparent`、CSS 色名 (`red` 等、英字1〜30文字) |
2169
+ | `background-color` | 同上 (ハイライトに使用) |
2170
+ | `font-size` | 数値 + 単位 (`px` / `em` / `rem` / `pt` / `%`) |
2171
+
2172
+ スタイル値に `url(...)` `expression(...)` `javascript:` `<` `>` を含むものは破棄される。`<span style="...">` で書式を組み合わせるときは `;` 区切りで複数プロパティを並べる:
2173
+
2174
+ ```html
2175
+ <p>通常 <strong>太字</strong> <em>斜体</em> <span style="color: #c00">赤字</span></p>
2176
+ <ul><li>項目1</li><li>項目2 <span style="background-color: yellow">ハイライト</span></li></ul>
2177
+ <ol><li>番号付き</li></ol>
2178
+ ```
2179
+
2180
+ サニタイザは未閉じタグを末尾で自動クローズし、`<` `>` `&` を含む生テキストは entity に escape する。生 JSON で `content` を組み立てるときは、引用符の escape (`\"`) と HTML entity の二重エスケープに注意。
2181
+
2118
2182
  **image:**
2119
2183
  ```json
2120
- { "src": "<workspace_files.id>", "fit": "contain" }
2184
+ { "src": "deck:<image_uuid>.<ext>", "fit": "contain" }
2121
2185
  ```
2122
2186
 
2123
- `src` UUID 形式なら表示時に Drive 内画像として解決される。外部URLも指定可。
2187
+ `fit` `"contain"` / `"cover"` / `"fill"` のみ。`src` の受け付け形式:
2188
+
2189
+ - `deck:<image_uuid>.<ext>` — デッキ専用 Storage 上の画像 (HQ UI から `POST .../decks/:deckId/images` でアップロード済みのもの)。レンダリング時にデッキ画像エンドポイントに解決される
2190
+ - `https://…` などの外部 URL — そのまま `<img src>` に渡される (pass-through)
2191
+ - 旧形式の `<workspace_files.id>` (bare UUID) は **非対応** (v3.61.0 で廃止)。古いデッキは作り直しが必要
2124
2192
 
2125
2193
  #### 座標系
2126
2194
 
@@ -2132,9 +2200,28 @@ POST レスポンス例:
2132
2200
 
2133
2201
  | Code | 意味 |
2134
2202
  |------|------|
2135
- | 400 | リクエスト不正 (`version` 欠落、`element_type` 不明、座標が数値でない等) |
2203
+ | 400 | リクエスト不正 (`version` 欠落、`element_type` 不明、座標が数値でない、`invalid_background` (オブジェクトでない / type 不明 / 必須フィールド欠落)、`style.*` 欠落・不正値等) |
2136
2204
  | 403 | `drive_not_enabled` (ワークスペースで Drive 未有効化) または `Minion is not a member` |
2137
2205
  | 404 | `not_found` (ID 間違いか別ワークスペースのリソース) |
2138
2206
  | 409 | `version_conflict` (他者の編集と衝突)、`name_conflict`、`order_key_collision` (同時挿入の競合、再送可) |
2139
2207
  | 410 | `deck_in_trash` (デッキがゴミ箱にある) |
2140
2208
 
2209
+ ### Accounting Receipts 🧪 (HQ, experimental)
2210
+
2211
+ `workspaces.feature_flags.experimental_accounting = true` のワークスペース向け。
2212
+ レシート (画像/PDF) の一覧取得・ダウンロード・取引紐付けをBearer認証で行える。
2213
+ **典型用途**: 人間が `/accounting/receipts` から大量アップロードした未仕訳レシートを
2214
+ ミニオン側で OCR → 仕訳作成 → 紐付け、という記帳ワークフロー。
2215
+
2216
+ | Method | Endpoint | 説明 |
2217
+ |--------|----------|-------------|
2218
+ | GET | `/api/minion/workspaces/:id/accounting/receipts` | レシート一覧。Query: `?attached=true\|false\|all` (default `false`), `?limit=100` (max 500) |
2219
+ | GET | `/api/minion/workspaces/:id/accounting/receipts/:receiptId` | メタデータ |
2220
+ | GET | `/api/minion/workspaces/:id/accounting/receipts/:receiptId/download` | 60秒有効な signed URL を返す (`{ url, mime_type, file_name, expires_in_seconds }`) |
2221
+ | PATCH | `/api/minion/workspaces/:id/accounting/receipts/:receiptId` | 取引と紐付け / 解除。Body: `{ entry_id: string \| null }`。entry_id 指定時は同じ book に属する取引でないと 400 |
2222
+
2223
+ レシートのMIME種別は `image/png`, `image/jpeg`, `image/webp`, `image/gif`, `image/heic`, `image/heif`, `application/pdf` のいずれか。
2224
+
2225
+ **未実装**: ミニオン向け仕訳作成 API (`POST /api/minion/workspaces/:id/accounting/entries`) は現状未実装。
2226
+ OCR後の仕訳作成は、当面ユーザー操作で `/accounting/new` から行うか、HQ管理者向けの Supabase session 経由のみ。
2227
+
@@ -918,7 +918,8 @@ meeting_end() # DELETE /api/minion/meetings/:id を裏で叩く
918
918
 
919
919
  - **JSON 全体を送る方式ではない。** 1要素単位で PATCH するため、ミニオンが扱うコンテキストは常に小さい
920
920
  - **1テキスト要素 = 単一スタイル。** 部分太字などはできない。スタイルを変えたい区間は別要素として配置する
921
- - **画像は Drive にアップロード済みであることが前提。** `POST .../drive/files` で画像をアップロードした後、その `workspace_files.id` `data.src` に貼る
921
+ - **画像は外部 URL またはデッキ画像参照のいずれか。** `data.src` には `"https://..."` (passthrough) か `"deck:<uuid>.<ext>"` (HQ UI から `POST .../decks/:deckId/images` でアップロード済みの形式) を指定する。旧来の `workspace_files.id` 形式は v3.61.0 で廃止された。ミニオン API には現状デッキ画像アップロードのミラーが無いため、ミニオン主導でデッキ内画像を増やしたい場合は外部 URL を使うか、人間ユーザーに HQ から事前アップロードしてもらう
922
+ - **背景 (`background`) はオブジェクト必須。** `{type: "color", value: "#..."}` または `{type: "image", src: "deck:..."}` のどちらか。文字列 `"#fff"` を直接渡すと 400 になる。詳細は `~/.minion/docs/api-reference.md` の `background ペイロード形式` を参照
922
923
  - **削除はソフト削除** (`DELETE .../decks/:deckId` で Drive ゴミ箱に入る)。誤削除は UI から復元できる
923
924
  - スライドマスター・テーマ・トランジション・アニメーションは MVP ではサポート外
924
925
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@geekbeer/minion",
3
- "version": "3.60.1",
3
+ "version": "3.60.5",
4
4
  "description": "AI Agent runtime for Minion - manages status and skill deployment on VPS",
5
5
  "main": "linux/server.js",
6
6
  "bin": {
@@ -1,65 +1,69 @@
1
1
  /**
2
- * Meeting Runner (Windows / WSL)
2
+ * Meeting Runner (Windows / native)
3
3
  *
4
- * Spawns a dedicated tmux session inside WSL per meeting invitation.
5
- * Mirrors linux/meeting-runner.js but routes commands through WSL since
6
- * Windows native tmux is not available.
4
+ * Spawns claude inside a node-pty session per meeting invitation. Mirrors
5
+ * linux/meeting-runner.js in behavior but uses node-pty + cmd.exe instead of
6
+ * tmux the same pattern as win/workflow-runner.js.
7
7
  *
8
- * Session naming: `mt-{meetingId.slice(0,8)}`
8
+ * WSL is intentionally NOT used: meeting participation is a pure HTTP bridge
9
+ * via the meeting MCP server (see core/meetings/meeting-mcp-server.js) with
10
+ * no Linux toolchain requirement, and claude CLI runs natively on Windows.
11
+ *
12
+ * Session naming: `mt-{meetingId.slice(0,8)}` (same as Linux for parity)
13
+ *
14
+ * Lifecycle:
15
+ * 1. Receive invitation from HQ via routes/meetings POST /api/meetings/invitations.
16
+ * 2. If an active session for this meeting already exists in-process,
17
+ * return early (idempotent: HQ may retry pushes).
18
+ * 3. Write temporary MCP config + kickoff prompt, then spawn claude via
19
+ * cmd.exe with --mcp-config and --allowedTools flags.
20
+ * 4. pty exits when claude exits (after meeting_leave is called or the
21
+ * meeting was deleted on HQ side); onExit cleans up tracking.
9
22
  */
10
23
 
11
- const { exec } = require('child_process')
12
- const { promisify } = require('util')
13
24
  const fs = require('fs').promises
25
+ const fsSync = require('fs')
14
26
  const os = require('os')
15
27
  const path = require('path')
16
- const execAsync = promisify(exec)
17
28
 
18
29
  const { config } = require('../core/config')
19
30
  const runningTasks = require('../core/lib/running-tasks')
20
31
  const logManager = require('../core/lib/log-manager')
32
+ const { stripAnsi } = require('../core/lib/strip-ansi')
21
33
  const { getActivePrimary } = require('../core/llm-plugins/lib/active')
22
34
  const { buildMeetingKickoffPrompt } = require('../core/meetings/meeting-prompt')
23
35
 
36
+ /**
37
+ * Active pty sessions keyed by session name.
38
+ * @type {Map<string, {pty: object, logStream: object|null, meetingId: string, startedAt: string}>}
39
+ */
40
+ const activeSessions = new Map()
41
+
24
42
  function generateSessionName(meetingId) {
25
43
  if (!meetingId) throw new Error('meetingId is required')
26
44
  return `mt-${String(meetingId).substring(0, 8)}`
27
45
  }
28
46
 
29
- function wslExec(cmd) {
30
- return execAsync(`wsl bash -lc ${JSON.stringify(cmd)}`)
31
- }
32
-
33
- async function tmuxHasSession(sessionName) {
34
- try {
35
- await wslExec(`tmux has-session -t "${sessionName}" 2>/dev/null`)
36
- return true
37
- } catch {
38
- return false
39
- }
47
+ function loadNodePty() {
48
+ try { return require('node-pty-prebuilt-multiarch') } catch {}
49
+ try { return require('node-pty') } catch {}
50
+ throw new Error(
51
+ 'node-pty is required for Windows meeting participation. ' +
52
+ 'Install with: npm install node-pty-prebuilt-multiarch'
53
+ )
40
54
  }
41
55
 
42
56
  async function listMeetingSessions() {
43
- try {
44
- const { stdout } = await wslExec(`tmux ls -F '#S' 2>/dev/null`)
45
- return stdout
46
- .split('\n')
47
- .map((s) => s.trim())
48
- .filter((s) => s.startsWith('mt-'))
49
- } catch {
50
- return []
51
- }
57
+ return Array.from(activeSessions.keys())
52
58
  }
53
59
 
54
60
  async function killMeetingSession(meetingId) {
55
61
  const sessionName = generateSessionName(meetingId)
56
- try {
57
- await wslExec(`tmux kill-session -t "${sessionName}" 2>/dev/null`)
58
- runningTasks.remove(sessionName)
59
- return true
60
- } catch {
61
- return false
62
- }
62
+ const session = activeSessions.get(sessionName)
63
+ if (!session) return false
64
+ try { session.pty.kill() } catch { /* ignore */ }
65
+ // onExit handler removes from activeSessions and runningTasks
66
+ return true
63
67
  }
64
68
 
65
69
  async function runMeeting({ meetingId, title, purpose, host, hqUrl, selfName, role }) {
@@ -69,17 +73,19 @@ async function runMeeting({ meetingId, title, purpose, host, hqUrl, selfName, ro
69
73
 
70
74
  const sessionName = generateSessionName(meetingId)
71
75
 
72
- if (await tmuxHasSession(sessionName)) {
73
- console.log(`[MeetingRunner-win] tmux session ${sessionName} already exists, skipping start`)
76
+ if (activeSessions.has(sessionName)) {
77
+ console.log(`[MeetingRunner-win] Session ${sessionName} already active, skipping start`)
74
78
  return { sessionName, started: false, success: true }
75
79
  }
76
80
 
77
81
  const tmpdir = os.tmpdir()
78
82
  const promptFile = path.join(tmpdir, `minion-meeting-prompt-${sessionName}.txt`)
79
83
  const mcpConfigFile = path.join(tmpdir, `minion-meeting-mcp-${sessionName}.json`)
80
- const execScript = path.join(tmpdir, `minion-meeting-exec-${sessionName}.sh`)
81
84
  const mcpServerPath = path.join(__dirname, '..', 'core', 'meetings', 'meeting-mcp-server.js')
82
85
 
86
+ console.log(`[MeetingRunner-win] Starting meeting ${meetingId} (${title})`)
87
+ console.log(`[MeetingRunner-win] Session: ${sessionName}`)
88
+
83
89
  try {
84
90
  await logManager.ensureLogDir()
85
91
 
@@ -93,9 +99,8 @@ async function runMeeting({ meetingId, title, purpose, host, hqUrl, selfName, ro
93
99
  })
94
100
  await fs.writeFile(promptFile, prompt, 'utf-8')
95
101
 
96
- // Use the minion's own config.HQ_URL when available. HQ-pushed hq_url is
97
- // only a fallback because the minion already has the correct URL set at
98
- // provisioning time.
102
+ // Use the minion's own config.HQ_URL set correctly at provisioning time.
103
+ // Falling back to the URL pushed by HQ only if local config is missing.
99
104
  const effectiveHqUrl = config.HQ_URL || hqUrl
100
105
  if (!effectiveHqUrl) {
101
106
  throw new Error('No HQ_URL configured (neither minion config nor HQ push)')
@@ -119,12 +124,15 @@ async function runMeeting({ meetingId, title, purpose, host, hqUrl, selfName, ro
119
124
  const primary = getActivePrimary()
120
125
  if (!primary || primary.name !== 'claude') {
121
126
  throw new Error(
122
- 'Meeting participation currently requires the claude primary LLM (MCP support).',
127
+ 'Meeting participation currently requires the claude primary LLM (MCP support). ' +
128
+ `Active primary: ${primary ? primary.name : 'none'}`,
123
129
  )
124
130
  }
125
131
 
126
- // See linux/meeting-runner.js for why --allowedTools is needed (-p mode
127
- // hangs on MCP permission prompts without this).
132
+ // --allowedTools: Claude Code defaults to prompting the user for every MCP
133
+ // tool call, which hangs in -p mode (no interactive UI). Pre-authorize the
134
+ // meeting MCP tools so claude can run the participation loop without
135
+ // approval prompts.
128
136
  const allowedTools = [
129
137
  'mcp__meeting__meeting_wait_for_next_message',
130
138
  'mcp__meeting__meeting_speak',
@@ -133,24 +141,67 @@ async function runMeeting({ meetingId, title, purpose, host, hqUrl, selfName, ro
133
141
  'mcp__meeting__meeting_end',
134
142
  ].join(',')
135
143
 
136
- await fs.writeFile(
137
- execScript,
138
- `#!/bin/bash\nexport HQ_URL="${effectiveHqUrl}"\nexport API_TOKEN="${config.API_TOKEN}"\nexport MINION_MEETING_ID="${meetingId}"\nclaude -p --mcp-config "${mcpConfigFile}" --allowedTools "${allowedTools}" < "${promptFile}"\n`,
139
- 'utf-8',
140
- )
144
+ // cmd.exe handles the `<` redirection for stdin from promptFile.
145
+ // `claude` resolves via PATH (claude.cmd shim on Windows).
146
+ const llmCommand = `claude -p --mcp-config "${mcpConfigFile}" --allowedTools "${allowedTools}" < "${promptFile}"`
147
+
148
+ const pty = loadNodePty()
149
+ const shell = process.env.COMSPEC || 'cmd.exe'
150
+ const shellArgs = ['/c', llmCommand]
151
+
152
+ const ptyProcess = pty.spawn(shell, shellArgs, {
153
+ name: 'xterm-256color',
154
+ cols: 200,
155
+ rows: 50,
156
+ cwd: config.HOME_DIR,
157
+ env: {
158
+ ...process.env,
159
+ HQ_URL: effectiveHqUrl,
160
+ API_TOKEN: config.API_TOKEN,
161
+ MINION_MEETING_ID: meetingId,
162
+ },
163
+ })
164
+
165
+ const logDir = logManager.LOG_DIR || path.join(config.HOME_DIR, '.minion', 'logs')
166
+ const logFile = path.join(logDir, `meeting-${meetingId}.log`)
167
+ let logStream = null
168
+ try {
169
+ logStream = fsSync.createWriteStream(logFile, { flags: 'a' })
170
+ } catch (err) {
171
+ console.error(`[MeetingRunner-win] Failed to open log file ${logFile}: ${err.message}`)
172
+ }
173
+
174
+ const session = {
175
+ pty: ptyProcess,
176
+ logStream,
177
+ meetingId,
178
+ startedAt: new Date().toISOString(),
179
+ }
180
+ activeSessions.set(sessionName, session)
181
+
182
+ ptyProcess.onData((data) => {
183
+ if (logStream) {
184
+ try { logStream.write(stripAnsi(data)) } catch { /* ignore */ }
185
+ }
186
+ })
141
187
 
142
- await wslExec(`chmod +x "${execScript}"`)
143
- await wslExec(`tmux new-session -d -s "${sessionName}" -x 200 -y 50`)
144
- await wslExec(`tmux set-option -t "${sessionName}" remain-on-exit on`)
145
- await wslExec(`tmux send-keys -t "${sessionName}" "bash ${execScript}" Enter`)
188
+ ptyProcess.onExit(({ exitCode }) => {
189
+ console.log(`[MeetingRunner-win] Session ${sessionName} exited (code: ${exitCode})`)
190
+ if (logStream) {
191
+ try { logStream.end() } catch { /* ignore */ }
192
+ }
193
+ activeSessions.delete(sessionName)
194
+ runningTasks.remove(sessionName)
195
+ })
146
196
 
147
197
  runningTasks.add({
148
198
  type: 'meeting',
149
199
  session_name: sessionName,
150
200
  meeting_id: meetingId,
151
- started_at: new Date().toISOString(),
201
+ started_at: session.startedAt,
152
202
  })
153
203
 
204
+ console.log(`[MeetingRunner-win] Meeting ${meetingId} session started (PID: ${ptyProcess.pid})`)
154
205
  return { sessionName, started: true, success: true }
155
206
  } catch (err) {
156
207
  console.error(`[MeetingRunner-win] Failed to start meeting ${meetingId}: ${err.message}`)
@@ -163,4 +214,5 @@ module.exports = {
163
214
  killMeetingSession,
164
215
  listMeetingSessions,
165
216
  generateSessionName,
217
+ activeSessions,
166
218
  }