@geekbeer/minion 3.55.1 → 3.58.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.
package/roles/engineer.md CHANGED
@@ -28,7 +28,9 @@
28
28
 
29
29
  1. **まずプロジェクトメモリーを検索** — `GET /api/project-memories?project_id=...&search=キーワード` で過去の知見を確認
30
30
  2. **自己解決不可なら即ヘルプスレッドを起票** — `POST /api/threads` で `thread_type: "help"` を作成。`attempted_resolution` に試したことを記載
31
- 3. **人間にしか解決できない問題は `mentions: ["user"]` で即エスカレーション** — 認証コード、パスワード、外部承認など
31
+ 3. **人間にしか解決できない問題は即エスカレーション** — 認証コード、パスワード、外部承認など
32
+ - 当事者が特定できる場合は **`GET /api/minion/me/project/<project_id>/members` で `user_id` を引いて `mentions: ["user:<auth_user_id>"]` で個別指名** (display_name 重複時の誤通知を避けるため)
33
+ - 誰でも対応可能な問題なら `mentions: ["user"]` (generic)
32
34
  4. **解決後はスレッドを resolve し、知見をプロジェクトメモリーに保存**
33
35
 
34
36
  **スキルを失敗終了させる前に、必ずヘルプスレッドでエスカレーションすること。**
package/roles/pm.md CHANGED
@@ -48,14 +48,18 @@ PMとして、ブロッカーの解決を主導する責務がある:
48
48
 
49
49
  ### 自分がブロッカーに遭遇した場合
50
50
  1. **プロジェクトメモリーを検索** — 過去の知見で解決できないか確認
51
- 2. **自己解決不可なら `mentions: ["user"]` でヘルプスレッドを起票** — PMが解決できない問題は人間にエスカレーション
51
+ 2. **自己解決不可ならヘルプスレッドを起票** — PMが解決できない問題は人間にエスカレーション
52
+ - 特定の人にしか分からない問題なら **`GET /api/minion/me/project/<project_id>/members` で `user_id` を引いて `mentions: ["user:<auth_user_id>"]` で個別指名**
53
+ - 誰でも良い性質の問題なら `mentions: ["user"]` (generic)
52
54
  3. **解決後はスレッドを resolve し、知見をプロジェクトメモリーに保存**
53
55
 
54
56
  ### エンジニアからのヘルプスレッドに対応する場合
55
57
  1. **thread_watcher が自動で通知するので、`role:pm` 宛のスレッドに回答する**
56
- 2. **自分で解決できない場合は `@user` メンション付きで人間にエスカレーション**
58
+ 2. **自分で解決できない場合は人間にエスカレーション** — 当事者が特定できるなら個別指名 (`user:<auth_user_id>`)、不明なら `user` (generic)
57
59
  3. **解決策が汎用的なら、プロジェクトメモリーへの保存を促す**
58
60
 
61
+ display_name はワークスペース内で一意とは限らない。同名問題を避けるため、**generic `user` より個別指名 (`user:<id>`) を優先する**こと。`user_id` の取得方法は `~/.minion/docs/api-reference.md` の「Project Members / Workspace Members」セクションを参照。
62
+
59
63
  詳細は `~/.minion/rules/core.md` の「Blocker Handling」セクションを参照。
60
64
 
61
65
  ## Routine (従来方式)
package/rules/core.md CHANGED
@@ -220,10 +220,11 @@ Routine 実行中は以下もtmuxセッション環境で利用可能:
220
220
  - `MINION_EXECUTION_ID` — 実行UUID
221
221
  - `MINION_ROUTINE_ID` — ルーティンUUID
222
222
  - `MINION_ROUTINE_NAME` — ルーティン名
223
+ - `MINION_ROUTINE_WORKSPACE_ID` — ルーティンが特定ワークスペースにバインドされている場合のワークスペースUUID(未バインドなら空文字)
223
224
 
224
- **変数**(ミニオン変数・プロジェクト変数)はスキル本文の `{{VAR_NAME}}` テンプレートとして実行時に展開される。スキル作成時にパラメータ化したい値は `{{変数名}}` で記述すること。展開優先順位: ミニオン変数 < プロジェクト変数(後者が優先)。
225
+ **変数**(ワークスペース変数・プロジェクト変数・ワークフロー変数・ミニオン変数)はスキル本文の `{{VAR_NAME}}` テンプレートとして実行時に展開される。スキル作成時にパラメータ化したい値は `{{変数名}}` で記述すること。展開優先順位: ワークスペース < プロジェクト < ワークフロー < ミニオン(後者が優先)。ミニオンが最終オーバーライドとなる設計で、ミニオン運用者がHQ側のデフォルトを差し替えられる経路を保証する。ミニオン変数はさらにミニオン全体スコープとワークスペース別スコープに分かれ、当該ワークスペースのコンテキストで動く実行時はWS別がミニオン全体を上書きする。`/api/variables/*` は `?workspace_id=<uuid>` でスコープ指定(省略時はミニオン全体)。
225
226
 
226
- **シークレット**(ミニオンシークレット)はサーバー起動時に `process.env` にロードされ、全子プロセスで環境変数 `$SECRET_NAME` として利用可能。APIキーやパスワード等の機密情報に使用する。シークレットは `{{VAR}}` テンプレートでは展開されない。
227
+ **シークレット**はワークスペース別にスコープされ、ミニオンローカルのSQLite (`secrets(workspace_id, key, value)`) に保存される。ワークスペース未指定(`workspace_id=''`)はミニオン全体のスコープで、サーバー起動時に `process.env` にロードされ全子プロセスで `$SECRET_NAME` として利用可能。ワークスペース別シークレット(`workspace_id=<uuid>`)は `process.env` にはロードされず、当該ワークスペースのコンテキストで動くランナー(ワークフロー/WSバインドされたルーティン/チャットセッション)にのみ実行時注入される。同名キーが両スコープに存在する場合はワークスペース別が優先。スキル側は常に `$KEY` で参照すればよく、どちらのスコープから来た値かを意識する必要はない。シークレット値はHQ DBに保存されることはなく、HQはpass-throughとして中継するのみ。
227
228
 
228
229
  デイリーログやメモリーから変数・シークレットの値を推測して使用してはならない。変数は `{{VAR_NAME}}` テンプレートとして定義し、シークレットは環境変数として参照すること。
229
230
 
@@ -321,7 +322,8 @@ API詳細は `~/.minion/docs/api-reference.md` の「Todos」セクションを
321
322
  "thread_type": "help",
322
323
  "title": "問題の要約(1行)",
323
324
  "content": "状況の詳細説明",
324
- "mentions": ["role:pm"],
325
+ "mentions": ["user:<auth_user_id>"], // 特定の人に聞くなら個別指名 (推奨)
326
+ // 誰でも良いなら ["user"] / ["role:pm"] 等
325
327
  "context": {
326
328
  "category": "auth|environment|external-service|information|approval",
327
329
  "attempted_resolution": "試行した内容"
@@ -335,7 +337,7 @@ API詳細は `~/.minion/docs/api-reference.md` の「Todos」セクションを
335
337
  "thread_type": "help",
336
338
  "title": "問題の要約(1行)",
337
339
  "content": "状況の詳細説明",
338
- "mentions": ["user"],
340
+ "mentions": ["user:<auth_user_id>"], // 個別指名 (推奨)。誰でも良いなら ["user"]
339
341
  "context": {
340
342
  "category": "auth|environment|external-service|information|approval",
341
343
  "attempted_resolution": "試行した内容"
@@ -365,18 +367,25 @@ API詳細は `~/.minion/docs/api-reference.md` の「Todos」セクションを
365
367
 
366
368
  ### メンションの使い分け
367
369
 
368
- | 状況 | メンション |
369
- |------|-----------|
370
- | PMに判断を仰ぐ | `["role:pm"]` |
371
- | エンジニアの知見が必要 | `["role:engineer"]` |
372
- | 特定ミニオンに聞く | `["minion:<minion_id>"]` |
373
- | 人間の介入が必要(認証コード、承認等) | `["user"]` |
374
- | チーム全体に共有 | `[]`(メンションなし) |
370
+ | 状況 | メンション | 備考 |
371
+ |------|-----------|------|
372
+ | 特定の人間を指名(推奨) | `["user:<auth_user_id>"]` | 事前に Members API で `user_id` を解決すること |
373
+ | PMに判断を仰ぐ | `["role:pm"]` | プロジェクト紐づきスレッドのみ有効 |
374
+ | エンジニアの知見が必要 | `["role:engineer"]` | プロジェクト紐づきスレッドのみ有効 |
375
+ | 特定ミニオンに聞く | `["minion:<minion_id>"]` | |
376
+ | 誰でも良いから人間に聞く | `["user"]` | プロジェクト紐づきならプロジェクト人間メンバー、紐づきがなければワークスペース人間メンバー全員 |
377
+ | チーム全体に共有 | `[]`(メンションなし) | |
378
+
379
+ **人間メンバーの user_id 解決:**
380
+ - プロジェクト紐づきスレッド: `GET /api/minion/me/project/<project_id>/members` で `humans[].user_id` を取得
381
+ - ワークスペーススレッド: `GET /api/minion/workspaces/<workspace_id>/members` で `humans[].user_id` を取得
382
+
383
+ display_name が同じユーザーが同一ワークスペースに複数いる可能性があるため、原則として個別指名 (`user:<id>`) を優先する。「誰でも良い」場合のみ generic `user` を使う。
375
384
 
376
385
  ### 重要なルール
377
386
 
378
387
  - **スレッド起票時は `attempted_resolution` を必ず含める。** 何を試したか不明だと、回答者が同じことを提案してしまう。
379
- - **人間にしか解決できない問題(認証コード入力、外部サービスのパスワード等)は即 `@user` メンション**で起票する。自己解決を試みて時間を浪費しないこと。
388
+ - **人間にしか解決できない問題(認証コード入力、外部サービスのパスワード等)は即メンション付き** で起票する。自己解決を試みて時間を浪費しないこと。特定の人にしか分からないことなら個別指名 (`user:<id>`)、誰でも良い性質の問題なら `user`(generic) を使う。
380
389
  - **ブロッカーが解決したらスレッドを `resolve` する。** 放置しない。
381
390
  - **解決策をプロジェクトメモリーに保存する。** 同じブロッカーに再度遭遇する他のミニオンの助けになる。
382
391
 
@@ -12,12 +12,14 @@ const { stripAnsi } = require('../core/lib/strip-ansi')
12
12
  const fs = require('fs').promises
13
13
  const fsSync = require('fs')
14
14
 
15
- const { config } = require('../core/config')
15
+ const { config, isHqConfigured } = require('../core/config')
16
+ const api = require('../core/api')
16
17
  const executionStore = require('../core/stores/execution-store')
17
18
  const routineStore = require('../core/stores/routine-store')
18
19
  const logManager = require('../core/lib/log-manager')
19
20
  const { activeSessions } = require('./workflow-runner')
20
21
  const { expandSkillTemplates, restoreSkillTemplates } = require('../core/lib/template-expander')
22
+ const { buildSpawnEnv } = require('../core/lib/session-env')
21
23
  const { getActivePrimary } = require('../core/llm-plugins/lib/active')
22
24
  const os = require('os')
23
25
 
@@ -28,6 +30,25 @@ function sleep(ms) {
28
30
  return new Promise((resolve) => setTimeout(resolve, ms))
29
31
  }
30
32
 
33
+ /**
34
+ * Fetch workspace-scoped variables for a routine from HQ.
35
+ * Mirrors the Linux runner; see that file for full docs.
36
+ */
37
+ async function fetchWorkspaceVars(workspaceId) {
38
+ if (!workspaceId || !isHqConfigured()) return {}
39
+ try {
40
+ const result = await api.request(`/workspaces/${workspaceId}/variables`)
41
+ const vars = {}
42
+ for (const v of (result?.variables || [])) {
43
+ if (v && typeof v.key === 'string') vars[v.key] = String(v.value ?? '')
44
+ }
45
+ return vars
46
+ } catch (err) {
47
+ console.error(`[RoutineRunner] Failed to fetch workspace vars (${workspaceId}): ${err.message}`)
48
+ return {}
49
+ }
50
+ }
51
+
31
52
  function generateSessionName(routineId, executionId) {
32
53
  const routineShort = routineId ? routineId.substring(0, 8) : 'manual'
33
54
  const execShort = executionId ? executionId.substring(0, 4) : ''
@@ -67,10 +88,15 @@ async function executeRoutineSession(routine, executionId, skillNames) {
67
88
  console.log(`[RoutineRunner] Session: ${sessionName}`)
68
89
  console.log(`[RoutineRunner] Log file: ${logFile}`)
69
90
 
70
- // Expand {{VAR}} templates in SKILL.md files with minion variables
91
+ // Fetch HQ workspace variables when the routine is bound to a workspace.
92
+ // Minion variables (minion-wide ∪ WS-scoped, resolved inside the expander
93
+ // via workspaceId) override these as the final layer.
94
+ const workspaceVars = await fetchWorkspaceVars(routine.workspace_id)
95
+
96
+ // Expand {{VAR}} templates in SKILL.md files
71
97
  let expandedOriginals = new Map()
72
98
  try {
73
- expandedOriginals = await expandSkillTemplates(skillNames)
99
+ expandedOriginals = await expandSkillTemplates(skillNames, workspaceVars, routine.workspace_id || '')
74
100
  if (expandedOriginals.size > 0) {
75
101
  console.log(`[RoutineRunner] Expanded templates in ${expandedOriginals.size} skill(s)`)
76
102
  }
@@ -101,14 +127,15 @@ async function executeRoutineSession(routine, executionId, skillNames) {
101
127
  throw new Error('No LLM configured. Set a Primary plugin via /api/llm/config or LLM_COMMAND in minion.env')
102
128
  }
103
129
 
104
- // PATH, HOME, USERPROFILE, and minion secrets are already set in
105
- // process.env at server startup. Per-execution identifiers are added here.
106
- const env = {
107
- ...process.env,
130
+ // PATH, HOME, USERPROFILE are inherited; workspace-scoped secrets are
131
+ // merged in via buildSpawnEnv() so a routine only sees secrets relevant
132
+ // to the workspace it is bound to.
133
+ const env = buildSpawnEnv(routine.workspace_id || '', {
108
134
  MINION_EXECUTION_ID: executionId,
109
135
  MINION_ROUTINE_ID: routine.id,
110
136
  MINION_ROUTINE_NAME: routine.name,
111
- }
137
+ MINION_ROUTINE_WORKSPACE_ID: routine.workspace_id || '',
138
+ })
112
139
 
113
140
  const logDir = path.dirname(logFile)
114
141
  await fs.mkdir(logDir, { recursive: true })
@@ -24,6 +24,7 @@ const executionStore = require('../core/stores/execution-store')
24
24
  const workflowStore = require('../core/stores/workflow-store')
25
25
  const logManager = require('../core/lib/log-manager')
26
26
  const { expandSkillTemplates, restoreSkillTemplates } = require('../core/lib/template-expander')
27
+ const { buildSpawnEnv } = require('../core/lib/session-env')
27
28
  const { getActivePrimary } = require('../core/llm-plugins/lib/active')
28
29
  const os = require('os')
29
30
 
@@ -142,10 +143,11 @@ async function executeWorkflowSession(workflow, executionId, skillNames, options
142
143
  console.log(`[WorkflowRunner] Log file: ${logFile}`)
143
144
  console.log(`[WorkflowRunner] HOME: ${homeDir}`)
144
145
 
145
- // Expand {{VAR}} templates in SKILL.md files with minion variables
146
+ // Expand {{VAR}} templates in SKILL.md files with minion variables effective
147
+ // for this workflow's workspace (minion-wide ∪ WS-scoped, WS wins).
146
148
  let expandedOriginals = new Map()
147
149
  try {
148
- expandedOriginals = await expandSkillTemplates(skillNames)
150
+ expandedOriginals = await expandSkillTemplates(skillNames, {}, workflow.workspace_id || '')
149
151
  if (expandedOriginals.size > 0) {
150
152
  console.log(`[WorkflowRunner] Expanded templates in ${expandedOriginals.size} skill(s)`)
151
153
  }
@@ -178,8 +180,9 @@ async function executeWorkflowSession(workflow, executionId, skillNames, options
178
180
  throw new Error('No LLM configured. Set a Primary plugin via /api/llm/config or LLM_COMMAND in minion.env')
179
181
  }
180
182
 
181
- // PATH, HOME, USERPROFILE, and minion secrets are already set in
182
- // process.env at server startup, so child processes inherit them automatically.
183
+ // PATH, HOME, USERPROFILE are inherited from process.env (set at startup);
184
+ // workspace-scoped secrets are merged in via buildSpawnEnv() so this
185
+ // session only sees secrets relevant to its workspace context.
183
186
 
184
187
  // Open log file for streaming writes
185
188
  const logDir = path.dirname(logFile)
@@ -195,7 +198,11 @@ async function executeWorkflowSession(workflow, executionId, skillNames, options
195
198
  cols: 200,
196
199
  rows: 50,
197
200
  cwd: homeDir,
198
- env: process.env,
201
+ env: buildSpawnEnv(workflow.workspace_id || '', {
202
+ MINION_EXECUTION_ID: executionId,
203
+ MINION_WORKFLOW_ID: workflow.id || '',
204
+ MINION_WORKFLOW_WORKSPACE_ID: workflow.workspace_id || '',
205
+ }),
199
206
  })
200
207
 
201
208
  // Track session