@geekbeer/minion 3.8.0 → 3.9.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/core/config.js CHANGED
@@ -75,6 +75,7 @@ const config = {
75
75
  HQ_URL: process.env.HQ_URL || '',
76
76
  API_TOKEN: process.env.API_TOKEN || '',
77
77
  MINION_ID: process.env.MINION_ID || '',
78
+ MINION_EMAIL: process.env.MINION_ID ? `m-${process.env.MINION_ID}@minion-agent.com` : '',
78
79
 
79
80
  // Server settings
80
81
  AGENT_PORT: parseInt(process.env.AGENT_PORT, 10) || 8080,
@@ -111,6 +112,12 @@ function validate() {
111
112
  } else {
112
113
  console.log('[Config] HQ not configured, running in standalone mode')
113
114
  }
115
+
116
+ // Expose MINION_EMAIL to child processes (e.g. Claude Code, Gemini CLI)
117
+ if (config.MINION_EMAIL) {
118
+ process.env.MINION_EMAIL = config.MINION_EMAIL
119
+ console.log(`[Config] Email: ${config.MINION_EMAIL}`)
120
+ }
114
121
  }
115
122
 
116
123
  /**
package/core/db.js CHANGED
@@ -425,6 +425,23 @@ function migrateSchema(db) {
425
425
 
426
426
  console.log('[DB] Migration 2 complete: emails FTS5 added')
427
427
  }
428
+
429
+ if (currentVersion < 3) {
430
+ console.log('[DB] Migration 3: Adding project_id to memories...')
431
+
432
+ db.exec(`
433
+ -- Add nullable project_id column to memories table.
434
+ -- NULL = universal memory (cross-project knowledge).
435
+ -- Set = project-scoped memory.
436
+ ALTER TABLE memories ADD COLUMN project_id TEXT DEFAULT NULL;
437
+
438
+ CREATE INDEX idx_memories_project_id ON memories(project_id);
439
+
440
+ INSERT INTO schema_version (version) VALUES (3);
441
+ `)
442
+
443
+ console.log('[DB] Migration 3 complete: memories.project_id added')
444
+ }
428
445
  }
429
446
 
430
447
  /**
@@ -42,8 +42,10 @@ async function runEndOfDay({ runQuickLlmCall, clearSession = false }) {
42
42
  - title: 短いタイトル
43
43
  - category: user(ユーザーの好み)/ feedback(フィードバック)/ project(プロジェクト情報)/ reference(参照情報)のいずれか
44
44
  - content: 詳細な内容
45
+ - project_id: この学びが特定プロジェクトに固有の場合はそのプロジェクトID(UUID)。汎用的なノウハウやユーザーの好みなど、プロジェクト横断で活用すべき学びの場合はnull
45
46
 
46
47
  学びがない場合は空配列にしてください。すでに常識的なことは含めないでください。
48
+ 特定のプロジェクトの技術スタック・ルール・設計判断などはproject_idを設定し、汎用的なスキルや知見はnullにしてください。
47
49
 
48
50
  会話:
49
51
  ${conversationText}
@@ -83,6 +85,7 @@ JSON形式のみで回答してください(\`\`\`jsonブロックは不要)
83
85
  title: learning.title,
84
86
  category: learning.category || 'reference',
85
87
  content: learning.content,
88
+ project_id: learning.project_id || null,
86
89
  })
87
90
  memoriesAdded++
88
91
  }
@@ -4,11 +4,17 @@
4
4
  * Provides CRUD + search for the minion's long-term memory entries.
5
5
  * Memory is stored in SQLite with FTS5 full-text search.
6
6
  *
7
+ * project_id scoping:
8
+ * - Entries with project_id are scoped to a specific project.
9
+ * - Entries without project_id are universal (cross-project knowledge).
10
+ * - Queries with ?project_id=xxx return scoped + universal entries.
11
+ *
7
12
  * Endpoints:
8
13
  * GET /api/memory - List all entries (or search with ?search=keyword)
14
+ * Optional: ?project_id=xxx to scope results
9
15
  * GET /api/memory/:id - Get a single entry
10
- * POST /api/memory - Create a new entry
11
- * PUT /api/memory/:id - Update an entry
16
+ * POST /api/memory - Create a new entry (optional project_id in body)
17
+ * PUT /api/memory/:id - Update an entry (optional project_id in body)
12
18
  * DELETE /api/memory/:id - Delete an entry
13
19
  */
14
20
 
@@ -28,13 +34,13 @@ async function memoryRoutes(fastify) {
28
34
  return { success: false, error: 'Unauthorized' }
29
35
  }
30
36
 
31
- const { search } = request.query || {}
37
+ const { search, project_id } = request.query || {}
32
38
  if (search) {
33
- const entries = memoryStore.searchEntries(search)
39
+ const entries = memoryStore.searchEntries(search, { projectId: project_id })
34
40
  return { success: true, entries }
35
41
  }
36
42
 
37
- const entries = memoryStore.listEntries()
43
+ const entries = memoryStore.listEntries({ projectId: project_id })
38
44
  return { success: true, entries }
39
45
  })
40
46
 
@@ -61,14 +67,14 @@ async function memoryRoutes(fastify) {
61
67
  return { success: false, error: 'Unauthorized' }
62
68
  }
63
69
 
64
- const { title, category, content } = request.body || {}
70
+ const { title, category, content, project_id } = request.body || {}
65
71
  if (!title || !content) {
66
72
  reply.code(400)
67
73
  return { success: false, error: 'title and content are required' }
68
74
  }
69
75
 
70
- const entry = memoryStore.saveEntry({ title, category, content })
71
- console.log(`[Memory] Created entry: ${entry.id} (${entry.title})`)
76
+ const entry = memoryStore.saveEntry({ title, category, content, project_id })
77
+ console.log(`[Memory] Created entry: ${entry.id} (${entry.title})${entry.project_id ? ` [project:${entry.project_id}]` : ' [universal]'}`)
72
78
  return { success: true, entry }
73
79
  })
74
80
 
@@ -85,15 +91,16 @@ async function memoryRoutes(fastify) {
85
91
  return { success: false, error: 'Entry not found' }
86
92
  }
87
93
 
88
- const { title, category, content } = request.body || {}
94
+ const { title, category, content, project_id } = request.body || {}
89
95
  const entry = memoryStore.saveEntry({
90
96
  id: request.params.id,
91
97
  title: title || existing.title,
92
98
  category: category || existing.category,
93
99
  content: content !== undefined ? content : existing.content,
100
+ project_id: project_id !== undefined ? project_id : existing.project_id,
94
101
  })
95
102
 
96
- console.log(`[Memory] Updated entry: ${entry.id} (${entry.title})`)
103
+ console.log(`[Memory] Updated entry: ${entry.id} (${entry.title})${entry.project_id ? ` [project:${entry.project_id}]` : ' [universal]'}`)
97
104
  return { success: true, entry }
98
105
  })
99
106
 
@@ -4,6 +4,11 @@
4
4
  * Supports full-text search via FTS5.
5
5
  *
6
6
  * Categories: user, feedback, project, reference
7
+ *
8
+ * project_id scoping:
9
+ * - NULL project_id = universal memory (cross-project knowledge/know-how)
10
+ * - Set project_id = project-scoped memory
11
+ * - When querying with a project_id, returns both scoped + universal entries
7
12
  */
8
13
 
9
14
  const crypto = require('crypto')
@@ -13,17 +18,31 @@ const VALID_CATEGORIES = ['user', 'feedback', 'project', 'reference']
13
18
 
14
19
  /**
15
20
  * List all memory entries (metadata + excerpt, no full body).
16
- * @returns {Array<{ id, title, category, created_at, updated_at, excerpt }>}
21
+ * @param {{ projectId?: string }} [opts] - Optional filter options
22
+ * - projectId: If set, returns entries for that project + universal (NULL) entries.
23
+ * If omitted, returns all entries.
24
+ * @returns {Array<{ id, title, category, project_id, created_at, updated_at, excerpt }>}
17
25
  */
18
- function listEntries() {
26
+ function listEntries(opts = {}) {
19
27
  const db = getDb()
20
- const rows = db.prepare(`
21
- SELECT id, title, category, created_at, updated_at,
28
+ const { projectId } = opts
29
+
30
+ if (projectId) {
31
+ return db.prepare(`
32
+ SELECT id, title, category, project_id, created_at, updated_at,
33
+ substr(content, 1, 200) as excerpt
34
+ FROM memories
35
+ WHERE project_id = ? OR project_id IS NULL
36
+ ORDER BY updated_at DESC
37
+ `).all(projectId)
38
+ }
39
+
40
+ return db.prepare(`
41
+ SELECT id, title, category, project_id, created_at, updated_at,
22
42
  substr(content, 1, 200) as excerpt
23
43
  FROM memories
24
44
  ORDER BY updated_at DESC
25
45
  `).all()
26
- return rows
27
46
  }
28
47
 
29
48
  /**
@@ -39,7 +58,8 @@ function loadEntry(id) {
39
58
 
40
59
  /**
41
60
  * Save (create or update) a memory entry.
42
- * @param {{ id?, title, category, content }} entry
61
+ * @param {{ id?, title, category, content, project_id? }} entry
62
+ * - project_id: UUID of the project this memory belongs to, or null/undefined for universal
43
63
  * @returns {object} The saved entry
44
64
  */
45
65
  function saveEntry(entry) {
@@ -47,6 +67,7 @@ function saveEntry(entry) {
47
67
  const now = new Date().toISOString()
48
68
  const id = entry.id || crypto.randomBytes(6).toString('hex')
49
69
  const category = VALID_CATEGORIES.includes(entry.category) ? entry.category : 'reference'
70
+ const projectId = entry.project_id || null
50
71
 
51
72
  const existing = db.prepare('SELECT created_at FROM memories WHERE id = ?').get(id)
52
73
 
@@ -55,20 +76,21 @@ function saveEntry(entry) {
55
76
  title: entry.title || 'Untitled',
56
77
  category,
57
78
  content: entry.content || '',
79
+ project_id: projectId,
58
80
  created_at: existing ? existing.created_at : now,
59
81
  updated_at: now,
60
82
  }
61
83
 
62
84
  if (existing) {
63
85
  db.prepare(`
64
- UPDATE memories SET title = ?, category = ?, content = ?, updated_at = ?
86
+ UPDATE memories SET title = ?, category = ?, content = ?, project_id = ?, updated_at = ?
65
87
  WHERE id = ?
66
- `).run(full.title, full.category, full.content, full.updated_at, full.id)
88
+ `).run(full.title, full.category, full.content, full.project_id, full.updated_at, full.id)
67
89
  } else {
68
90
  db.prepare(`
69
- INSERT INTO memories (id, title, category, content, created_at, updated_at)
70
- VALUES (?, ?, ?, ?, ?, ?)
71
- `).run(full.id, full.title, full.category, full.content, full.created_at, full.updated_at)
91
+ INSERT INTO memories (id, title, category, content, project_id, created_at, updated_at)
92
+ VALUES (?, ?, ?, ?, ?, ?, ?)
93
+ `).run(full.id, full.title, full.category, full.content, full.project_id, full.created_at, full.updated_at)
72
94
  }
73
95
 
74
96
  return full
@@ -89,11 +111,14 @@ function deleteEntry(id) {
89
111
  * Search memory entries using FTS5 full-text search (trigram tokenizer).
90
112
  * Falls back to LIKE for queries shorter than 3 characters (trigram minimum).
91
113
  * @param {string} query - Search query
92
- * @param {number} limit - Max results
93
- * @returns {Array<{ id, title, category, content, created_at, updated_at }>}
114
+ * @param {{ limit?: number, projectId?: string }} [opts] - Search options
115
+ * - limit: Max results (default 20)
116
+ * - projectId: If set, returns matches for that project + universal (NULL) entries
117
+ * @returns {Array<{ id, title, category, project_id, content, created_at, updated_at }>}
94
118
  */
95
- function searchEntries(query, limit = 20) {
119
+ function searchEntries(query, opts = {}) {
96
120
  const db = getDb()
121
+ const { limit = 20, projectId } = typeof opts === 'number' ? { limit: opts } : opts
97
122
  const terms = query.split(/\s+/).filter(Boolean)
98
123
  if (terms.length === 0) return []
99
124
 
@@ -108,11 +133,18 @@ function searchEntries(query, limit = 20) {
108
133
  const like = `%${t}%`
109
134
  return [like, like]
110
135
  })
136
+
137
+ let projectFilter = ''
138
+ if (projectId) {
139
+ projectFilter = ' AND (project_id = ? OR project_id IS NULL)'
140
+ params.push(projectId)
141
+ }
142
+
111
143
  params.push(limit)
112
144
  return db.prepare(`
113
- SELECT id, title, category, content, created_at, updated_at
145
+ SELECT id, title, category, project_id, content, created_at, updated_at
114
146
  FROM memories
115
- WHERE ${conditions}
147
+ WHERE ${conditions}${projectFilter}
116
148
  ORDER BY updated_at DESC
117
149
  LIMIT ?
118
150
  `).all(...params)
@@ -120,8 +152,21 @@ function searchEntries(query, limit = 20) {
120
152
 
121
153
  // FTS5 trigram search: wrap each term in double quotes for exact substring match
122
154
  const sanitized = terms.map(t => `"${t.replace(/"/g, '')}"`).join(' ')
155
+
156
+ if (projectId) {
157
+ return db.prepare(`
158
+ SELECT m.id, m.title, m.category, m.project_id, m.content, m.created_at, m.updated_at
159
+ FROM memories m
160
+ JOIN memories_fts f ON m.rowid = f.rowid
161
+ WHERE memories_fts MATCH ?
162
+ AND (m.project_id = ? OR m.project_id IS NULL)
163
+ ORDER BY rank
164
+ LIMIT ?
165
+ `).all(sanitized, projectId, limit)
166
+ }
167
+
123
168
  return db.prepare(`
124
- SELECT m.id, m.title, m.category, m.content, m.created_at, m.updated_at
169
+ SELECT m.id, m.title, m.category, m.project_id, m.content, m.created_at, m.updated_at
125
170
  FROM memories m
126
171
  JOIN memories_fts f ON m.rowid = f.rowid
127
172
  WHERE memories_fts MATCH ?
@@ -133,18 +178,22 @@ function searchEntries(query, limit = 20) {
133
178
  /**
134
179
  * Get a context snippet suitable for chat injection.
135
180
  * Returns the most important memory entries as a formatted string.
136
- * @param {number} maxChars - Maximum characters to return
181
+ * @param {{ maxChars?: number, projectId?: string }} [opts] - Options
182
+ * - maxChars: Maximum characters to return (default 2000)
183
+ * - projectId: If set, includes project-scoped + universal entries
137
184
  * @returns {string}
138
185
  */
139
- function getContextSnippet(maxChars = 2000) {
140
- const entries = listEntries()
186
+ function getContextSnippet(opts = {}) {
187
+ const { maxChars = 2000, projectId } = typeof opts === 'number' ? { maxChars: opts } : opts
188
+ const entries = listEntries({ projectId })
141
189
  if (entries.length === 0) return ''
142
190
 
143
191
  const parts = []
144
192
  let totalLen = 0
145
193
 
146
194
  for (const e of entries) {
147
- const line = `[${e.category}] ${e.title}: ${e.excerpt}`
195
+ const scope = e.project_id ? '[project-scoped]' : '[universal]'
196
+ const line = `${scope} [${e.category}] ${e.title}: ${e.excerpt}`
148
197
  if (totalLen + line.length > maxChars) break
149
198
  parts.push(line)
150
199
  totalLen += line.length + 1
@@ -83,19 +83,26 @@ Full-text search supported via FTS5.
83
83
 
84
84
  | Method | Endpoint | Description |
85
85
  |--------|----------|-------------|
86
- | GET | `/api/memory` | List all memory entries (id, title, category, excerpt, timestamps) |
86
+ | GET | `/api/memory` | List all memory entries (id, title, category, project_id, excerpt, timestamps) |
87
87
  | GET | `/api/memory?search=keyword` | Full-text search on title and content (FTS5) |
88
+ | GET | `/api/memory?project_id=xxx` | List entries scoped to project + universal entries |
89
+ | GET | `/api/memory?search=keyword&project_id=xxx` | Search within project scope + universal |
88
90
  | GET | `/api/memory/:id` | Get full memory entry |
89
- | POST | `/api/memory` | Create memory entry. Body: `{title, category, content}` |
90
- | PUT | `/api/memory/:id` | Update memory entry. Body: `{title?, category?, content?}` |
91
+ | POST | `/api/memory` | Create memory entry. Body: `{title, category, content, project_id?}` |
92
+ | PUT | `/api/memory/:id` | Update memory entry. Body: `{title?, category?, content?, project_id?}` |
91
93
  | DELETE | `/api/memory/:id` | Delete a memory entry |
92
94
 
95
+ **Project scoping**: `project_id` is optional. When set, the entry is scoped to that project.
96
+ When `project_id` is omitted (NULL), the entry is universal (cross-project knowledge).
97
+ Queries with `?project_id=xxx` return both project-scoped AND universal entries.
98
+
93
99
  POST/PUT body:
94
100
  ```json
95
101
  {
96
102
  "title": "ユーザーはレビューで日本語を好む",
97
103
  "category": "user",
98
- "content": "コードレビューのコメントは日本語で記述すること。"
104
+ "content": "コードレビューのコメントは日本語で記述すること。",
105
+ "project_id": null
99
106
  }
100
107
  ```
101
108
 
@@ -443,6 +450,94 @@ POST `/api/project-memories` body:
443
450
  3. 該当なし → `POST /api/threads` でブロッカー起票(プロジェクトなら `scope: "project"` + `project_id`、ルーティンなら `scope: "workspace"`)
444
451
  4. 解決後 → プロジェクトスレッドの場合は `POST /api/project-memories` で知見を蓄積
445
452
 
453
+ ### Email
454
+
455
+ ミニオン専用メールアドレス (`m-{MINION_ID}@minion-agent.com`) 宛のメールを管理する。
456
+ メールは Cloudflare Email Worker → HQ → ミニオンの経路で自動受信・保存される。
457
+ 添付ファイルはセキュリティ上、人間が HQ ダッシュボードで承認するまでデータを取得できない。
458
+
459
+ | Method | Endpoint | Description |
460
+ |--------|----------|-------------|
461
+ | GET | `/api/email/inbox/summary` | 未読・合計件数 (`{unread, total, latest_at}`) |
462
+ | GET | `/api/email/inbox` | メール一覧(フィルター対応) |
463
+ | GET | `/api/email/inbox/:id` | メール詳細(添付メタデータ含む) |
464
+ | PUT | `/api/email/inbox/:id` | 既読/未読を更新 |
465
+ | DELETE | `/api/email/inbox/:id` | メールを削除 |
466
+ | POST | `/api/email/inbox/:id/attachments/:attachmentId/approve` | 添付ファイルを承認(HQダッシュボード用) |
467
+ | GET | `/api/email/inbox/:id/attachments/:attachmentId` | 添付ファイルを取得(承認済みのみ) |
468
+
469
+ GET `/api/email/inbox` query parameters:
470
+
471
+ | Param | Description |
472
+ |-------|-------------|
473
+ | `is_read` | `0` (未読) or `1` (既読) でフィルター |
474
+ | `from_address` | 送信元アドレスで部分一致検索 |
475
+ | `search` | 件名・本文でテキスト検索(3文字以上でFTS5使用) |
476
+ | `limit` | 取得件数(デフォルト: 50) |
477
+
478
+ GET `/api/email/inbox/summary` response:
479
+ ```json
480
+ {
481
+ "success": true,
482
+ "summary": { "unread": 3, "total": 12, "latest_at": "2026-04-01T10:30:00Z" }
483
+ }
484
+ ```
485
+
486
+ GET `/api/email/inbox` response:
487
+ ```json
488
+ {
489
+ "success": true,
490
+ "emails": [
491
+ {
492
+ "id": "abc123",
493
+ "from_address": "user@example.com",
494
+ "to_address": "m-uuid@minion-agent.com",
495
+ "subject": "タスクの依頼",
496
+ "body_text": "...",
497
+ "body_html": "...",
498
+ "received_at": "2026-04-01T10:30:00Z",
499
+ "is_read": 0,
500
+ "labels": []
501
+ }
502
+ ]
503
+ }
504
+ ```
505
+
506
+ GET `/api/email/inbox/:id` response (添付ファイル付き):
507
+ ```json
508
+ {
509
+ "success": true,
510
+ "email": {
511
+ "id": "abc123",
512
+ "from_address": "user@example.com",
513
+ "to_address": "m-uuid@minion-agent.com",
514
+ "subject": "資料送付",
515
+ "body_text": "...",
516
+ "body_html": "...",
517
+ "received_at": "2026-04-01T10:30:00Z",
518
+ "is_read": 0,
519
+ "labels": [],
520
+ "attachments": [
521
+ {
522
+ "id": "att456",
523
+ "email_id": "abc123",
524
+ "filename": "report.pdf",
525
+ "content_type": "application/pdf",
526
+ "size_bytes": 102400,
527
+ "approved": 0
528
+ }
529
+ ]
530
+ }
531
+ }
532
+ ```
533
+
534
+ PUT `/api/email/inbox/:id` body:
535
+ ```json
536
+ { "is_read": 1 }
537
+ ```
538
+
539
+ Note: 既読メールは受信後90日で自動削除される。未読メールは保持される。
540
+
446
541
  ### Commands
447
542
 
448
543
  | Method | Endpoint | Description |
@@ -246,6 +246,9 @@ async function buildContextPrefix(message, context, sessionId) {
246
246
  '# メモリ検索(キーワードで全文検索)',
247
247
  `curl -H "Authorization: Bearer $API_TOKEN" "${baseUrl}/api/memory?search=キーワード"`,
248
248
  '',
249
+ '# プロジェクトスコープでメモリ検索(プロジェクト固有 + 汎用メモリを返す)',
250
+ `curl -H "Authorization: Bearer $API_TOKEN" "${baseUrl}/api/memory?search=キーワード&project_id=プロジェクトUUID"`,
251
+ '',
249
252
  '# メモリ一覧',
250
253
  `curl -H "Authorization: Bearer $API_TOKEN" ${baseUrl}/api/memory`,
251
254
  '',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@geekbeer/minion",
3
- "version": "3.8.0",
3
+ "version": "3.9.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
@@ -27,6 +27,19 @@ Minion
27
27
  - **Routine**: ミニオンスコープ。ミニオンローカルの定期タスク。
28
28
  - ミニオンは複数プロジェクトに `pm` または `engineer` として参加できる。
29
29
 
30
+ ## Email
31
+
32
+ 各ミニオンには専用メールアドレス `m-{MINION_ID}@minion-agent.com` が割り当てられている。
33
+ 受信メールはローカルSQLiteに自動保存され、Agent APIで確認できる。
34
+
35
+ - **受信確認**: `GET /api/email/inbox/summary` で未読件数を確認
36
+ - **メール一覧**: `GET /api/email/inbox` でメール一覧を取得(フィルター・検索対応)
37
+ - **メール詳細**: `GET /api/email/inbox/:id` で本文・添付ファイルを確認
38
+ - **既読管理**: `PUT /api/email/inbox/:id` で既読/未読を切り替え
39
+ - **添付ファイル**: 添付ファイルはセキュリティ上、人間がHQダッシュボードで承認するまでダウンロード不可
40
+
41
+ API の詳細仕様は `~/.minion/docs/api-reference.md` の「Email」セクションを参照。
42
+
30
43
  ## Available Tools
31
44
 
32
45
  ### CLI
@@ -127,6 +140,7 @@ Note: Codex CLI の `.codex/` ディレクトリはLLMからの直接編集が
127
140
  | `MINION_ID` | UUID assigned by HQ |
128
141
  | `AGENT_PORT` | Agent HTTP port (default: 8080) |
129
142
  | `MINION_USER` | System user running the agent |
143
+ | `MINION_EMAIL` | Minion email address (`m-{MINION_ID}@minion-agent.com`) |
130
144
 
131
145
  Routine 実行中は以下もtmuxセッション環境で利用可能:
132
146
  - `MINION_EXECUTION_ID` — 実行UUID
@@ -6,7 +6,8 @@
6
6
  "Edit",
7
7
  "WebSearch",
8
8
  "WebFetch",
9
- "mcp__playwright__*"
9
+ "mcp__playwright__*",
10
+ "mcp__desktop-commander__*"
10
11
  ],
11
12
  "deny": [
12
13
  "Bash(sudo *)",
@@ -208,6 +208,9 @@ async function buildContextPrefix(message, context, sessionId) {
208
208
  '# メモリ検索(キーワードで全文検索)',
209
209
  `curl -H "Authorization: Bearer $API_TOKEN" "${baseUrl}/api/memory?search=キーワード"`,
210
210
  '',
211
+ '# プロジェクトスコープでメモリ検索(プロジェクト固有 + 汎用メモリを返す)',
212
+ `curl -H "Authorization: Bearer $API_TOKEN" "${baseUrl}/api/memory?search=キーワード&project_id=プロジェクトUUID"`,
213
+ '',
211
214
  '# メモリ一覧',
212
215
  `curl -H "Authorization: Bearer $API_TOKEN" ${baseUrl}/api/memory`,
213
216
  '',