@geekbeer/minion 3.59.10 → 3.60.4
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/docs/api-reference.md +163 -0
- package/docs/task-guides.md +32 -0
- package/package.json +1 -1
- package/win/meeting-runner.js +106 -54
package/docs/api-reference.md
CHANGED
|
@@ -2043,3 +2043,166 @@ hq note search <project_id> "キーワード"
|
|
|
2043
2043
|
参加中の操作 (GET state / long-poll messages / speak / leave / end / heartbeat) は MCP ツール (`meeting_get_state`, `meeting_wait_for_next_message`, `meeting_speak`, `meeting_leave`, `meeting_end`) として claude に公開されるため、直接叩く必要はない。会議の**作成**だけはルーティン/スキルから明示的に叩く必要がある (作成後は HQ がプッシュした招待で各ミニオンに mt-* tmux セッションが立ち上がる)。
|
|
2044
2044
|
|
|
2045
2045
|
**leave と end の違い**: `meeting_leave` は自分だけ退席するアクション (ルームは残る)、`meeting_end` (DELETE) はルームごと削除して全員解散させるアクション (ホストミニオン限定)。自分が招集した会議が終わったら必ず `meeting_end` を呼ばないとルームが孤児として残る。
|
|
2046
|
+
|
|
2047
|
+
### Workspace Drive (HQ, experimental)
|
|
2048
|
+
|
|
2049
|
+
ワークスペース単位のファイル管理機能。`experimental_drive` フィーチャーフラグが ON のワークスペースで利用可能。すべてのエンドポイントは `Authorization: Bearer ${API_TOKEN}` + ワークスペースメンバーであるミニオンに限定。
|
|
2050
|
+
|
|
2051
|
+
| Method | Endpoint | Description |
|
|
2052
|
+
|--------|----------|-------------|
|
|
2053
|
+
| GET | `/api/minion/workspaces/:id/drive/files` | ファイル一覧。Query: `?folder_id=<uuid\|null>`, `?search=<keyword>` |
|
|
2054
|
+
| POST | `/api/minion/workspaces/:id/drive/files` | ファイルアップロード (`multipart/form-data`: `file`, `folder_id?`, `name?`) |
|
|
2055
|
+
| GET | `/api/minion/workspaces/:id/drive/files/:fileId` | ファイル metadata |
|
|
2056
|
+
| GET | `/api/minion/workspaces/:id/drive/files/:fileId/download` | 60秒有効な signed URL を返す |
|
|
2057
|
+
|
|
2058
|
+
### Slide Decks 🧪 (HQ, experimental)
|
|
2059
|
+
|
|
2060
|
+
Drive 内のスライドデッキ。**スライドは Storage ではなく DB テーブルで管理** され、要素単位で API を叩ける。多人数同時編集とミニオン操作を前提に、各 PATCH は楽観ロック (`version` 必須) でガードされる。
|
|
2061
|
+
|
|
2062
|
+
**前提**: 対象ワークスペースで `experimental_drive` が ON、ミニオンが `workspace_minions` のメンバーであること。
|
|
2063
|
+
|
|
2064
|
+
#### デッキ操作
|
|
2065
|
+
|
|
2066
|
+
| Method | Endpoint | Description |
|
|
2067
|
+
|--------|----------|-------------|
|
|
2068
|
+
| GET | `/api/minion/workspaces/:id/drive/decks` | デッキ一覧 (非ゴミ箱、最大200件) |
|
|
2069
|
+
| POST | `/api/minion/workspaces/:id/drive/decks` | デッキ作成。Body: `{name, folder_id?, width?, height?}`。初期空白スライド1枚付き |
|
|
2070
|
+
| GET | `/api/minion/workspaces/:id/drive/decks/:deckId` | デッキメタ + スライド一覧 (要素は含まれない、軽量レスポンス)。`deckId` には `slide_decks.id` か `workspace_files.id` どちらでも可 |
|
|
2071
|
+
| DELETE | `/api/minion/workspaces/:id/drive/decks/:deckId` | ソフト削除 (Drive ゴミ箱へ移動) |
|
|
2072
|
+
|
|
2073
|
+
POST レスポンス例:
|
|
2074
|
+
```json
|
|
2075
|
+
{
|
|
2076
|
+
"deck": { "id": "...", "workspace_file_id": "...", "width": 1920, "height": 1080 },
|
|
2077
|
+
"workspaceFileId": "<file-uuid>",
|
|
2078
|
+
"firstSlideId": "<slide-uuid>",
|
|
2079
|
+
"mime_type": "application/vnd.geekbeer.slides"
|
|
2080
|
+
}
|
|
2081
|
+
```
|
|
2082
|
+
|
|
2083
|
+
#### スライド操作
|
|
2084
|
+
|
|
2085
|
+
| Method | Endpoint | Description |
|
|
2086
|
+
|--------|----------|-------------|
|
|
2087
|
+
| POST | `/api/minion/workspaces/:id/drive/decks/:deckId/slides` | スライド追加。Body: `{position?, background?, speaker_notes?}` (`position` 省略時は末尾)。`background` の形式は下記参照 |
|
|
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` 必須。`background` の形式は下記参照。`is_skipped=true` でプレゼン時に飛ばされる(順序は保持) |
|
|
2090
|
+
| DELETE | `/api/minion/workspaces/:id/drive/slides/:slideId` | スライド削除 (要素は CASCADE) |
|
|
2091
|
+
|
|
2092
|
+
#### 要素操作 (text / image)
|
|
2093
|
+
|
|
2094
|
+
| Method | Endpoint | Description |
|
|
2095
|
+
|--------|----------|-------------|
|
|
2096
|
+
| POST | `/api/minion/workspaces/:id/drive/slides/:slideId/elements` | 要素追加。Body: `{element_type: "text"\|"image", x, y, width, height, rotation?, z_index?, data}` |
|
|
2097
|
+
| PATCH | `/api/minion/workspaces/:id/drive/elements/:elementId` | 要素更新。Body: `{version, x?, y?, width?, height?, rotation?, data?, order_key?}`。`version` 必須、`element_type` は不変 |
|
|
2098
|
+
| DELETE | `/api/minion/workspaces/:id/drive/elements/:elementId` | 要素削除 |
|
|
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
|
+
|
|
2117
|
+
#### data ペイロード形式
|
|
2118
|
+
|
|
2119
|
+
**text:**
|
|
2120
|
+
```json
|
|
2121
|
+
{
|
|
2122
|
+
"content": "Hello",
|
|
2123
|
+
"style": {
|
|
2124
|
+
"fontFamily": "Inter, sans-serif",
|
|
2125
|
+
"fontSize": 32,
|
|
2126
|
+
"fontWeight": 400,
|
|
2127
|
+
"fontStyle": "normal",
|
|
2128
|
+
"color": "#111827",
|
|
2129
|
+
"textAlign": "left",
|
|
2130
|
+
"lineHeight": 1.4
|
|
2131
|
+
}
|
|
2132
|
+
}
|
|
2133
|
+
```
|
|
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
|
+
|
|
2182
|
+
**image:**
|
|
2183
|
+
```json
|
|
2184
|
+
{ "src": "deck:<image_uuid>.<ext>", "fit": "contain" }
|
|
2185
|
+
```
|
|
2186
|
+
|
|
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 で廃止)。古いデッキは作り直しが必要
|
|
2192
|
+
|
|
2193
|
+
#### 座標系
|
|
2194
|
+
|
|
2195
|
+
- 内部座標は **1920×1080 ピクセル固定** (デッキの `width` / `height` で参照可能)。左上が原点
|
|
2196
|
+
- 表示時は CSS transform で viewport にフィットする
|
|
2197
|
+
- `rotation` は中心基準の度数 (MVP では UI 未対応だが API では受け付ける)
|
|
2198
|
+
|
|
2199
|
+
#### エラーコード
|
|
2200
|
+
|
|
2201
|
+
| Code | 意味 |
|
|
2202
|
+
|------|------|
|
|
2203
|
+
| 400 | リクエスト不正 (`version` 欠落、`element_type` 不明、座標が数値でない、`invalid_background` (オブジェクトでない / type 不明 / 必須フィールド欠落)、`style.*` 欠落・不正値等) |
|
|
2204
|
+
| 403 | `drive_not_enabled` (ワークスペースで Drive 未有効化) または `Minion is not a member` |
|
|
2205
|
+
| 404 | `not_found` (ID 間違いか別ワークスペースのリソース) |
|
|
2206
|
+
| 409 | `version_conflict` (他者の編集と衝突)、`name_conflict`、`order_key_collision` (同時挿入の競合、再送可) |
|
|
2207
|
+
| 410 | `deck_in_trash` (デッキがゴミ箱にある) |
|
|
2208
|
+
|
package/docs/task-guides.md
CHANGED
|
@@ -891,6 +891,38 @@ meeting_end() # DELETE /api/minion/meetings/:id を裏で叩く
|
|
|
891
891
|
# → 全参加ミニオンに終了通知が push され、各 mt-* tmux が終了する
|
|
892
892
|
```
|
|
893
893
|
|
|
894
|
+
## スライド作成・編集 🧪 (experimental)
|
|
895
|
+
|
|
896
|
+
ワークスペース Drive にスライドデッキを作成・編集する。「資料作って」「スライドにまとめて」と頼まれた時はこのワークフローを使う。専用スキル `create-slide-deck` / `edit-slide-deck` が用意されており、これらが優先。スキルが手元に無い場合は API 仕様 (`Slide Decks` セクション) を直接叩く。
|
|
897
|
+
|
|
898
|
+
### 新規作成 (推奨フロー)
|
|
899
|
+
|
|
900
|
+
1. **アウトラインを先に決める。** 1枚あたりのタイトルと主要メッセージを箇条書きにする。長くても5-10スライド程度を目安に
|
|
901
|
+
2. デッキを作成: `POST /api/minion/workspaces/:id/drive/decks` (Body: `{name}`)。初期空白スライド1枚が `firstSlideId` で返る
|
|
902
|
+
3. 各スライドに **テキスト要素** を1つずつ追加: `POST .../slides/:slideId/elements`
|
|
903
|
+
- タイトル: `{x: 160, y: 80, width: 1600, height: 160, data: {content, style: {fontSize: 56, fontWeight: 700, ...}}}`
|
|
904
|
+
- 本文: `{x: 160, y: 280, width: 1600, height: 700, data: {content, style: {fontSize: 32, ...}}}`
|
|
905
|
+
4. 2枚目以降: `POST .../decks/:deckId/slides` でスライドを追加し、手順3を繰り返す
|
|
906
|
+
5. 完了したらノートまたはスレッドで起票者にデッキ URL (`${HQ_URL}/drive/slides/<workspaceFileId>`) を共有する
|
|
907
|
+
|
|
908
|
+
座標系は 1920×1080 ピクセル固定。詳細は `~/.minion/docs/api-reference.md` の `Slide Decks` セクションを参照。
|
|
909
|
+
|
|
910
|
+
### 部分編集
|
|
911
|
+
|
|
912
|
+
1. `GET .../decks/:deckId` でスライド一覧 (要素なし、軽量) を取得
|
|
913
|
+
2. 対象スライドの中身を `GET .../slides/:slideId` で取得 (要素の `version` も返る)
|
|
914
|
+
3. 編集対象の要素を `PATCH .../elements/:elementId` で更新。**Body に `version` 必須**
|
|
915
|
+
4. 409 (`version_conflict`) が返ったら手順2から再取得してリトライ
|
|
916
|
+
|
|
917
|
+
### 注意点
|
|
918
|
+
|
|
919
|
+
- **JSON 全体を送る方式ではない。** 1要素単位で PATCH するため、ミニオンが扱うコンテキストは常に小さい
|
|
920
|
+
- **1テキスト要素 = 単一スタイル。** 部分太字などはできない。スタイルを変えたい区間は別要素として配置する
|
|
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 ペイロード形式` を参照
|
|
923
|
+
- **削除はソフト削除** (`DELETE .../decks/:deckId` で Drive ゴミ箱に入る)。誤削除は UI から復元できる
|
|
924
|
+
- スライドマスター・テーマ・トランジション・アニメーションは MVP ではサポート外
|
|
925
|
+
|
|
894
926
|
## ツール・MCPサーバーのインストール
|
|
895
927
|
|
|
896
928
|
スキルが `requires` で宣言している MCP サーバーや CLI ツールが不足している場合は、`~/.minion/docs/environment-setup.md` の手順に従ってインストールする。
|
package/package.json
CHANGED
package/win/meeting-runner.js
CHANGED
|
@@ -1,65 +1,69 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Meeting Runner (Windows /
|
|
2
|
+
* Meeting Runner (Windows / native)
|
|
3
3
|
*
|
|
4
|
-
* Spawns a
|
|
5
|
-
*
|
|
6
|
-
*
|
|
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
|
-
*
|
|
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
|
|
30
|
-
return
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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
|
-
|
|
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
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
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 (
|
|
73
|
-
console.log(`[MeetingRunner-win]
|
|
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
|
|
97
|
-
//
|
|
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
|
-
//
|
|
127
|
-
// hangs
|
|
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
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
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
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
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:
|
|
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
|
}
|