@geekbeer/minion 3.36.0 → 3.40.2

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.
Files changed (40) hide show
  1. package/core/db/helpers.js +18 -0
  2. package/core/db/index.js +146 -0
  3. package/core/db/migrations/000_initial_schema.js +157 -0
  4. package/core/db/migrations/001_fts_trigram.js +78 -0
  5. package/core/db/migrations/002_emails_fts.js +41 -0
  6. package/core/db/migrations/003_memories_project_id.js +17 -0
  7. package/core/db/migrations/004_chat_sessions_workspace.js +18 -0
  8. package/core/db/migrations/005_todos_session_injection.js +19 -0
  9. package/core/db/migrations/006_daily_logs_workspace.js +69 -0
  10. package/core/db/migrations/007_workspace_scoping.js +29 -0
  11. package/core/db/migrations/008_todos_workspace.js +22 -0
  12. package/core/db/migrations/index.js +41 -0
  13. package/core/lib/config-warnings.js +16 -8
  14. package/core/lib/end-of-day.js +30 -14
  15. package/core/lib/reflection-scheduler.js +23 -9
  16. package/core/lib/thread-watcher.js +3 -0
  17. package/core/routes/daily-logs.js +64 -27
  18. package/core/routes/routines.js +6 -2
  19. package/core/routes/skills.js +10 -5
  20. package/core/routes/todos.js +20 -7
  21. package/core/routes/workflows.js +17 -7
  22. package/core/stores/daily-log-store.js +61 -30
  23. package/core/stores/execution-store.js +40 -18
  24. package/core/stores/routine-store.js +32 -14
  25. package/core/stores/todo-store.js +37 -10
  26. package/core/stores/workflow-store.js +34 -13
  27. package/docs/api-reference.md +68 -27
  28. package/linux/routes/chat.js +14 -9
  29. package/linux/routes/directives.js +5 -4
  30. package/linux/routine-runner.js +42 -7
  31. package/linux/workflow-runner.js +3 -1
  32. package/package.json +4 -2
  33. package/roles/engineer.md +0 -1
  34. package/rules/core.md +1 -0
  35. package/scripts/new-migration.js +53 -0
  36. package/win/routes/chat.js +14 -9
  37. package/win/routes/directives.js +5 -1
  38. package/win/routine-runner.js +40 -4
  39. package/win/workflow-runner.js +2 -0
  40. package/core/db.js +0 -583
@@ -54,22 +54,40 @@ hq list workspaces
54
54
 
55
55
  ### Workflows
56
56
 
57
+ v3.38.0 以降、workflows はワークスペース単位でスコープされる。各workflowレコードは `workspace_id` を持ち、未指定は `""`(未所属/legacy)として扱われる。
58
+
57
59
  | Method | Endpoint | Description |
58
60
  |--------|----------|-------------|
59
- | GET | `/api/workflows` | List all local workflows with next_run |
60
- | POST | `/api/workflows` | Receive/upsert workflows (from HQ or local) |
61
+ | GET | `/api/workflows` | List local workflows with next_run. Optional `?workspace_id=` filter (omit for cross-workspace view) |
62
+ | POST | `/api/workflows` | Receive/upsert workflows (from HQ). Each incoming workflow should carry `workspace_id` |
61
63
  | POST | `/api/workflows/push/:name` | Push local workflow to HQ |
62
- | POST | `/api/workflows/fetch/:name` | Fetch workflow from HQ and deploy locally (+ pipeline skills) |
64
+ | POST | `/api/workflows/fetch/:name` | Fetch workflow from HQ and deploy locally (+ pipeline skills). HQ response includes `workspace_id` |
63
65
  | GET | `/api/workflows/remote` | List workflows on HQ |
64
66
  | DELETE | `/api/workflows/:id` | Remove a local workflow |
65
67
  | POST | `/api/workflows/trigger` | Manual trigger. Body: `{workflow_id}` |
66
68
 
69
+ ### Routines
70
+
71
+ v3.38.0 以降、routines はワークスペース単位でスコープされる。各routineレコードは `workspace_id` を持ち、未指定は `""`(未所属/legacy)として扱われる。
72
+
73
+ | Method | Endpoint | Description |
74
+ |--------|----------|-------------|
75
+ | GET | `/api/routines` | List local routines with next_run. Optional `?workspace_id=` filter (omit for cross-workspace view) |
76
+ | POST | `/api/routines` | Receive/upsert routines. Each incoming routine should carry `workspace_id` |
77
+ | POST | `/api/routines/sync` | Pull routines from HQ (HQ currently returns empty — routines are minion-local) |
78
+ | PUT | `/api/routines/:id/schedule` | Update cron/is_active |
79
+ | DELETE | `/api/routines/:id` | Remove a routine |
80
+ | POST | `/api/routines/bulk-toggle` | Set is_active for all routines |
81
+ | POST | `/api/routines/trigger` | Manual trigger. Body: `{routine_id}` or `{routine_name}` |
82
+
67
83
  ### Executions
68
84
 
85
+ v3.38.0 以降、executions は親 workflow / routine から `workspace_id` を継承する。
86
+
69
87
  | Method | Endpoint | Description |
70
88
  |--------|----------|-------------|
71
- | GET | `/api/executions` | List execution history (`?limit=50`, `?workflow_id=`) |
72
- | GET | `/api/executions/:id` | Get single execution |
89
+ | GET | `/api/executions` | List execution history (`?limit=50`, `?workflow_id=`, optional `?workspace_id=`) |
90
+ | GET | `/api/executions/:id` | Get single execution (includes `workspace_id`) |
73
91
  | GET | `/api/executions/:id/log` | Get execution log content (`?tail=N`) |
74
92
  | POST | `/api/executions/:id/outcome` | Report outcome (no auth). Body: `{outcome, summary?, details?}` |
75
93
 
@@ -176,22 +194,26 @@ Response (list):
176
194
 
177
195
  ### Daily Logs (Short-term Memory)
178
196
 
179
- Daily conversation summaries stored in SQLite (`$DATA_DIR/minion.db`).
180
- Generated via end-of-day processing or manual creation.
181
- Full-text search supported via FTS5.
197
+ Daily conversation summaries stored in SQLite (`$DATA_DIR/minion.db`), **scoped by workspace**.
198
+ One entry per `(workspace_id, date)` multiple workspaces can have distinct logs for the same day.
199
+ Generated via end-of-day processing or manual creation. Full-text search supported via FTS5.
200
+
201
+ All endpoints require a `workspace_id` identifying the scope. Pass an empty string `""` to address
202
+ legacy / unassigned logs created before workspace scoping (v3.37.0).
182
203
 
183
204
  | Method | Endpoint | Description |
184
205
  |--------|----------|-------------|
185
- | GET | `/api/daily-logs` | List all logs (date + size, newest first) |
186
- | GET | `/api/daily-logs?search=keyword` | Full-text search on log content (FTS5) |
187
- | POST | `/api/daily-logs` | Create a daily log. Body: `{date, content}` |
188
- | GET | `/api/daily-logs/:date` | Get a specific day's log content |
189
- | PUT | `/api/daily-logs/:date` | Update a daily log. Body: `{content}` |
190
- | DELETE | `/api/daily-logs/:date` | Delete a specific day's log |
206
+ | GET | `/api/daily-logs?workspace_id=<id>` | List logs for a workspace (date + size, newest first) |
207
+ | GET | `/api/daily-logs?workspace_id=<id>&search=keyword` | FTS5 search scoped to a workspace |
208
+ | POST | `/api/daily-logs` | Create a daily log. Body: `{workspace_id, date, content}` |
209
+ | GET | `/api/daily-logs/:date?workspace_id=<id>` | Get a specific day's log for a workspace |
210
+ | PUT | `/api/daily-logs/:date` | Update a daily log. Body: `{workspace_id, content}` |
211
+ | DELETE | `/api/daily-logs/:date?workspace_id=<id>` | Delete a specific day's log |
191
212
 
192
213
  POST body:
193
214
  ```json
194
215
  {
216
+ "workspace_id": "ws_abc123",
195
217
  "date": "2026-03-12",
196
218
  "content": "## 今日やったこと\n- Feature X を実装\n- Bug Y を修正"
197
219
  }
@@ -200,6 +222,7 @@ POST body:
200
222
  PUT body:
201
223
  ```json
202
224
  {
225
+ "workspace_id": "ws_abc123",
203
226
  "content": "## 今日やったこと\n- Feature X を実装(更新版)"
204
227
  }
205
228
  ```
@@ -219,16 +242,26 @@ Response (list):
219
242
 
220
243
  | Method | Endpoint | Description |
221
244
  |--------|----------|-------------|
222
- | POST | `/api/chat/end-of-day` | Generate daily log + extract memories from conversation |
245
+ | POST | `/api/chat/end-of-day` | Generate daily log for one workspace + extract memories from its conversation |
223
246
 
224
- Body: `{ "clear_session": false }` (optional, defaults to false)
247
+ Body:
248
+ ```json
249
+ {
250
+ "workspace_id": "ws_abc123",
251
+ "clear_session": false
252
+ }
253
+ ```
254
+
255
+ `workspace_id` is required. If no conversation exists for that workspace that day, a stub log
256
+ ("本日、このワークスペースでの会話はありませんでした。") is saved so idle days are still recorded.
225
257
 
226
258
  Response:
227
259
  ```json
228
260
  {
229
261
  "success": true,
230
262
  "daily_log": "2026-03-12",
231
- "memory_entries_added": 2
263
+ "memory_entries_added": 2,
264
+ "had_conversation": true
232
265
  }
233
266
  ```
234
267
 
@@ -255,16 +288,22 @@ curl -X PUT /api/config/env \
255
288
  The scheduler starts automatically on server boot if `REFLECTION_TIME` is configured.
256
289
  Changes via the config API take effect immediately (no restart required).
257
290
 
291
+ On each fire, the scheduler iterates all known workspaces (plus the `""` legacy bucket) and runs
292
+ end-of-day processing per workspace. Workspaces without conversation that day get a stub log so
293
+ the idle day is recorded.
294
+
258
295
  ### Todos
259
296
 
260
297
  ミニオンローカルのToDoリスト。SQLiteに永続化され、HQにも同期される。
261
298
 
299
+ v3.39.0 以降、todos はワークスペース単位でスコープされる。`POST /api/todos` 時に `workspace_id` が必須(未所属/legacy なら `""` を明示)。
300
+
262
301
  | Method | Endpoint | Description |
263
302
  |--------|----------|-------------|
264
- | GET | `/api/todos` | List todos. Query: `status`, `priority`, `project_id`, `source_type`, `session_id`, `limit` |
265
- | GET | `/api/todos/summary` | Status counts |
266
- | GET | `/api/todos/:id` | Get single todo |
267
- | POST | `/api/todos` | Create. Body: `{title, description?, priority?, source_type?, source_id?, project_id?, due_at?, session_id?, data?}` |
303
+ | GET | `/api/todos` | List todos. Query: `status`, `priority`, `project_id`, `source_type`, `session_id`, optional `workspace_id`, `limit` |
304
+ | GET | `/api/todos/summary` | Status counts. Optional `?workspace_id=` scopes the counts |
305
+ | GET | `/api/todos/:id` | Get single todo (includes `workspace_id`) |
306
+ | POST | `/api/todos` | Create. Body: `{title, workspace_id, description?, priority?, source_type?, source_id?, project_id?, due_at?, session_id?, data?}` — `workspace_id` is **required** |
268
307
  | PUT | `/api/todos/:id` | Update any field including `status` |
269
308
  | DELETE | `/api/todos/:id` | Delete |
270
309
 
@@ -276,10 +315,10 @@ Changes via the config API take effect immediately (no restart required).
276
315
 
277
316
  **セッション紐づけ例**:
278
317
  ```bash
279
- # 作成時にsession_idを指定すると、このセッションのチャットに自動で再掲される
318
+ # 作成時はworkspace_id必須(未所属/legacyなら"")。session_idを指定すると、このセッションのチャットに自動で再掲される
280
319
  curl -X POST -H "Authorization: Bearer $API_TOKEN" -H "Content-Type: application/json" \
281
320
  http://localhost:8080/api/todos \
282
- -d '{"title": "レポートを保存", "session_id": "'$SESSION_ID'", "priority": "high"}'
321
+ -d '{"title": "レポートを保存", "workspace_id": "'$WORKSPACE_ID'", "session_id": "'$SESSION_ID'", "priority": "high"}'
283
322
 
284
323
  # 完了マーク(即座に更新すること)
285
324
  curl -X PUT -H "Authorization: Bearer $API_TOKEN" -H "Content-Type: application/json" \
@@ -298,9 +337,11 @@ curl -X PUT -H "Authorization: Bearer $API_TOKEN" -H "Content-Type: application/
298
337
 
299
338
  Allowed keys: `LLM_COMMAND`, `REFLECTION_TIME`
300
339
 
301
- ### LLM Plugins (opt-in)
340
+ ### LLM Plugins
302
341
 
303
- プラグイン方式の LLM 設定。`primary` を設定すると有効化される。未設定の場合は従来の `LLM_COMMAND` 経路で動作する。設定は `~/minion/llm/config.json` にファイルとして保存される (env var に依存しないため quote 破損バグの影響を受けない)。
342
+ プラグイン方式の LLM 設定。ワークフロー/ルーティン実行には `primary` の設定が必須。`primary` が未設定の場合、またはその名前が `enabled` に含まれていない場合はダッシュボードにエラーが表示される (heartbeat 経由)。設定は `~/minion/llm/config.json` にファイルとして保存される (env var に依存しないため quote 破損バグの影響を受けない)。
343
+
344
+ > **Note:** 旧 `LLM_COMMAND` 経路は obsolete。近々削除予定なので、新規ミニオンは必ず LLM プラグインを設定すること。
304
345
 
305
346
  | Method | Endpoint | Description |
306
347
  |--------|----------|-------------|
@@ -826,7 +867,7 @@ push するとパイプライン内のスキル名が `skill_version_id` に解
826
867
  ```json
827
868
  {
828
869
  "name": "my-workflow",
829
- "pipeline_skill_names": ["skill-1", "skill-2", "execution-report"],
870
+ "pipeline_skill_names": ["skill-1", "skill-2"],
830
871
  "pipeline": [
831
872
  {
832
873
  "skill_version_id": "uuid",
@@ -1707,7 +1748,7 @@ Response:
1707
1748
  {
1708
1749
  "id": "uuid",
1709
1750
  "name": "routine-name",
1710
- "pipeline_skill_names": ["skill-1", "execution-report"],
1751
+ "pipeline_skill_names": ["skill-1"],
1711
1752
  "content": "...",
1712
1753
  "is_active": true,
1713
1754
  "cron_expression": "0 9 * * 1-5",
@@ -229,16 +229,21 @@ ${indexed}`
229
229
  return { success: true, carry_over: carryOver }
230
230
  })
231
231
 
232
- // POST /api/chat/end-of-day - Generate daily log + extract memories
232
+ // POST /api/chat/end-of-day - Generate daily log + extract memories for one workspace
233
233
  fastify.post('/api/chat/end-of-day', async (request, reply) => {
234
234
  if (!verifyToken(request)) {
235
235
  reply.code(401)
236
236
  return { success: false, error: 'Unauthorized' }
237
237
  }
238
238
 
239
- const { clear_session = false } = request.body || {}
239
+ const { workspace_id, clear_session = false } = request.body || {}
240
+ if (workspace_id !== '' && typeof workspace_id !== 'string') {
241
+ reply.code(400)
242
+ return { success: false, error: 'workspace_id is required' }
243
+ }
240
244
 
241
245
  const result = await runEndOfDay({
246
+ workspaceId: workspace_id,
242
247
  runQuickLlmCall,
243
248
  clearSession: clear_session,
244
249
  })
@@ -318,14 +323,14 @@ async function buildContextPrefix(message, context, sessionId, workspaceId) {
318
323
  '# メモリ詳細(IDを指定)',
319
324
  `curl -H "Authorization: Bearer $API_TOKEN" ${baseUrl}/api/memory/{id}`,
320
325
  '',
321
- '# デイリーログ検索',
322
- `curl -H "Authorization: Bearer $API_TOKEN" "${baseUrl}/api/daily-logs?search=キーワード"`,
326
+ '# デイリーログ検索(workspace_idは「現在のワークスペース」のIDを指定。未所属なら空文字)',
327
+ `curl -H "Authorization: Bearer $API_TOKEN" "${baseUrl}/api/daily-logs?workspace_id=現在のWSのID&search=キーワード"`,
323
328
  '',
324
- '# デイリーログ一覧',
325
- `curl -H "Authorization: Bearer $API_TOKEN" ${baseUrl}/api/daily-logs`,
329
+ '# デイリーログ一覧(現在のワークスペース)',
330
+ `curl -H "Authorization: Bearer $API_TOKEN" "${baseUrl}/api/daily-logs?workspace_id=現在のWSのID"`,
326
331
  '',
327
332
  '# 特定日のデイリーログ取得',
328
- `curl -H "Authorization: Bearer $API_TOKEN" ${baseUrl}/api/daily-logs/YYYY-MM-DD`,
333
+ `curl -H "Authorization: Bearer $API_TOKEN" "${baseUrl}/api/daily-logs/YYYY-MM-DD?workspace_id=現在のWSのID"`,
329
334
  '```',
330
335
  '',
331
336
  '参照すべきタイミング:',
@@ -366,10 +371,10 @@ async function buildContextPrefix(message, context, sessionId, workspaceId) {
366
371
  '',
367
372
  'ToDo APIの使い方:',
368
373
  '```bash',
369
- '# ToDo作成(session_idは後でセッションIDが判明してから設定)',
374
+ '# ToDo作成(workspace_idは必須。現在のワークスペースのIDを指定。未所属の場合は空文字 "")',
370
375
  `curl -X POST http://localhost:${port}/api/todos \\`,
371
376
  ' -H "Authorization: Bearer $API_TOKEN" -H "Content-Type: application/json" \\',
372
- ' -d \'{"title": "ステップの説明", "session_id": "SESSION_ID", "priority": "normal"}\'',
377
+ ' -d \'{"title": "ステップの説明", "workspace_id": "現在のWSのID", "session_id": "SESSION_ID", "priority": "normal"}\'',
373
378
  '',
374
379
  '# ToDo完了',
375
380
  `curl -X PUT http://localhost:${port}/api/todos/{id} \\`,
@@ -87,6 +87,7 @@ async function directiveRoutes(fastify) {
87
87
  const startedAt = new Date().toISOString()
88
88
  const logFile = logManager.getLogPath(effectiveExecutionId)
89
89
  const workflowName = context?.workflow_name || skill_name
90
+ const workspaceId = context?.workspace_id || ''
90
91
 
91
92
  // Save initial execution record
92
93
  await executionStore.save({
@@ -94,6 +95,7 @@ async function directiveRoutes(fastify) {
94
95
  skill_name,
95
96
  workflow_id: null,
96
97
  workflow_name: workflowName,
98
+ workspace_id: workspaceId,
97
99
  status: 'running',
98
100
  outcome: null,
99
101
  started_at: startedAt,
@@ -110,14 +112,12 @@ async function directiveRoutes(fastify) {
110
112
 
111
113
  try {
112
114
  // Execute as a single-skill workflow
113
- // skipExecutionReport: the orchestration template has its own
114
- // completion report (section 5), so /execution-report is redundant
115
- // and would use the wrong (local) execution ID.
116
115
  const result = await workflowRunner.runWorkflow({
117
116
  id: effectiveExecutionId,
118
117
  name: workflowName,
119
118
  pipeline_skill_names: [skill_name],
120
- }, { skipExecutionReport: true })
119
+ workspace_id: workspaceId,
120
+ })
121
121
 
122
122
  console.log(`[Directive] Execution completed: ${skill_name} (success: ${result.execution_id ? 'yes' : 'no'})`)
123
123
  } catch (err) {
@@ -127,6 +127,7 @@ async function directiveRoutes(fastify) {
127
127
  skill_name,
128
128
  workflow_id: null,
129
129
  workflow_name: workflowName,
130
+ workspace_id: workspaceId,
130
131
  status: 'failed',
131
132
  outcome: 'failure',
132
133
  started_at: startedAt,
@@ -5,8 +5,7 @@
5
5
  *
6
6
  * Key design:
7
7
  * - All skills in a routine run in ONE CLI session (context preserved)
8
- * - /execution-report is automatically appended to report outcome
9
- * - Marker file written before session start for skill-to-execution mapping
8
+ * - Outcome is reported to the local API by the runner itself based on CLI exit code
10
9
  * - Same tmux execution model as workflow-runner
11
10
  */
12
11
 
@@ -66,7 +65,7 @@ async function executeRoutineSession(routine, executionId, skillNames) {
66
65
  const homeDir = config.HOME_DIR
67
66
  const sessionName = generateSessionName(routine.id, executionId)
68
67
 
69
- // Build prompt: run each skill in sequence, then execution-report
68
+ // Build prompt: run each skill in sequence
70
69
  const primary = getActivePrimary()
71
70
  const formatSkill = primary && typeof primary.formatSkillInvocation === 'function'
72
71
  ? primary.formatSkillInvocation.bind(primary)
@@ -78,8 +77,7 @@ async function executeRoutineSession(routine, executionId, skillNames) {
78
77
  ? `## Context\n\n${routine.context}\n\n---\n\n`
79
78
  : ''
80
79
 
81
- const reportSkill = formatSkill('execution-report')
82
- const prompt = `${contextPrefix}Run the following skills in order: ${skillCommands}. After completing all skills, run ${reportSkill} to report the results.`
80
+ const prompt = `${contextPrefix}Run the following skills in order: ${skillCommands}.`
83
81
 
84
82
  // Exit code file to capture CLI result
85
83
  const exitCodeFile = `/tmp/tmux-exit-${sessionName}`
@@ -88,7 +86,7 @@ async function executeRoutineSession(routine, executionId, skillNames) {
88
86
  const logFile = logManager.getLogPath(executionId)
89
87
 
90
88
  console.log(`[RoutineRunner] Executing routine: ${routine.name}`)
91
- console.log(`[RoutineRunner] Skills: ${skillNames.join(' → ')} → execution-report`)
89
+ console.log(`[RoutineRunner] Skills: ${skillNames.join(' → ')}`)
92
90
  console.log(`[RoutineRunner] tmux session: ${sessionName}`)
93
91
  console.log(`[RoutineRunner] Log file: ${logFile}`)
94
92
 
@@ -267,6 +265,7 @@ async function runRoutine(routine) {
267
265
  skill_name: pipelineSkillNames.join(' → '),
268
266
  routine_id: routine.id,
269
267
  routine_name: routine.name,
268
+ workspace_id: routine.workspace_id || '',
270
269
  status: 'running',
271
270
  outcome: null,
272
271
  started_at: startedAt,
@@ -280,6 +279,7 @@ async function runRoutine(routine) {
280
279
  const result = await executeRoutineSession(routine, executionId, pipelineSkillNames)
281
280
 
282
281
  const completedAt = new Date().toISOString()
282
+ const outcome = result.success ? 'success' : 'failure'
283
283
 
284
284
  // Save: routine completed
285
285
  await executionStore.save({
@@ -287,8 +287,9 @@ async function runRoutine(routine) {
287
287
  skill_name: pipelineSkillNames.join(' → '),
288
288
  routine_id: routine.id,
289
289
  routine_name: routine.name,
290
+ workspace_id: routine.workspace_id || '',
290
291
  status: result.success ? 'completed' : 'failed',
291
- outcome: result.success ? null : 'failure',
292
+ outcome,
292
293
  started_at: startedAt,
293
294
  completed_at: completedAt,
294
295
  parent_execution_id: null,
@@ -296,6 +297,40 @@ async function runRoutine(routine) {
296
297
  log_file: logFile,
297
298
  })
298
299
 
300
+ // Extract summary from execution log file (captured via tmux pipe-pane)
301
+ let summary = result.success
302
+ ? `All skills completed successfully: ${pipelineSkillNames.join(', ')}`
303
+ : `Routine failed: ${result.error || 'unknown error'}`
304
+ try {
305
+ const logData = await logManager.readLog(executionId, { tail: 200 })
306
+ if (logData && logData.content && logData.content.trim().length > 0) {
307
+ const MAX_SUMMARY_LENGTH = 10000
308
+ const content = logData.content.trim()
309
+ summary = content.length > MAX_SUMMARY_LENGTH
310
+ ? content.slice(-MAX_SUMMARY_LENGTH)
311
+ : content
312
+ }
313
+ } catch (err) {
314
+ console.error(`[RoutineRunner] Failed to read log for summary: ${err.message}`)
315
+ }
316
+
317
+ // Report outcome via local API
318
+ try {
319
+ const resp = await fetch(`http://localhost:${config.AGENT_PORT || 8080}/api/executions/${executionId}/outcome`, {
320
+ method: 'POST',
321
+ headers: { 'Content-Type': 'application/json' },
322
+ body: JSON.stringify({
323
+ outcome,
324
+ summary,
325
+ }),
326
+ })
327
+ if (!resp.ok) {
328
+ console.error(`[RoutineRunner] Failed to report outcome: ${resp.status}`)
329
+ }
330
+ } catch (err) {
331
+ console.error(`[RoutineRunner] Failed to report outcome: ${err.message}`)
332
+ }
333
+
299
334
  // Update last_run in local store
300
335
  await routineStore.updateLastRun(routine.id)
301
336
 
@@ -333,6 +333,7 @@ async function runWorkflow(workflow, options = {}) {
333
333
  skill_name: pipelineSkillNames.join(' → '),
334
334
  workflow_id: workflow.id,
335
335
  workflow_name: workflow.name,
336
+ workspace_id: workflow.workspace_id || '',
336
337
  status: 'running',
337
338
  outcome: null,
338
339
  started_at: startedAt,
@@ -355,6 +356,7 @@ async function runWorkflow(workflow, options = {}) {
355
356
  skill_name: pipelineSkillNames.join(' → '),
356
357
  workflow_id: workflow.id,
357
358
  workflow_name: workflow.name,
359
+ workspace_id: workflow.workspace_id || '',
358
360
  status: result.success ? 'completed' : 'failed',
359
361
  outcome,
360
362
  started_at: startedAt,
@@ -381,7 +383,7 @@ async function runWorkflow(workflow, options = {}) {
381
383
  console.error(`[WorkflowRunner] Failed to read log for summary: ${err.message}`)
382
384
  }
383
385
 
384
- // Report outcome via local API (same data the execution-report skill used to send)
386
+ // Report outcome via local API
385
387
  try {
386
388
  const resp = await fetch(`http://localhost:${config.AGENT_PORT || 8080}/api/executions/${executionId}/outcome`, {
387
389
  method: 'POST',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@geekbeer/minion",
3
- "version": "3.36.0",
3
+ "version": "3.40.2",
4
4
  "description": "AI Agent runtime for Minion - manages status and skill deployment on VPS",
5
5
  "main": "linux/server.js",
6
6
  "bin": {
@@ -19,12 +19,14 @@
19
19
  "roles/",
20
20
  "docs/",
21
21
  "settings/",
22
+ "scripts/",
22
23
  ".env.example"
23
24
  ],
24
25
  "scripts": {
25
26
  "start": "node linux/server.js",
26
27
  "start:win": "node win/server.js",
27
- "postinstall": "node postinstall.js"
28
+ "postinstall": "node postinstall.js",
29
+ "db:migration:new": "node scripts/new-migration.js"
28
30
  },
29
31
  "dependencies": {
30
32
  "croner": "^9.0.0",
package/roles/engineer.md CHANGED
@@ -6,7 +6,6 @@
6
6
 
7
7
  - **スキル実行**: 割り当てられたワークフローステップ(スキル)を確実に実行する
8
8
  - **タスク完了**: スキルの指示に従い、成果物を生成する
9
- - **結果報告**: 実行結果を `/execution-report` または API 経由で報告する
10
9
 
11
10
  ## 重要なルール
12
11
 
package/rules/core.md CHANGED
@@ -218,6 +218,7 @@ Routine 実行中は以下もtmuxセッション環境で利用可能:
218
218
  - **Todoは「1往復で完了する粒度」に分解して登録する。** 大きいタスクは複数のTodoに分ける。粒度を小さく保てば、完了マークを圧縮に奪われにくい。
219
219
  - **完了したら即座に done にマークする。** まとめて更新しない。`PUT /api/todos/:id` で `status=done`。
220
220
  - **チャットセッション内で作成するTodoには必ず `session_id` を含める。** プロンプト冒頭の `[現在のチャットセッションID]` の値を使う。紐づいた未完了Todoは次ターン以降に自動で再掲される(圧縮を跨いでも失われない)。
221
+ - **`POST /api/todos` は `workspace_id` が必須(v3.39.0〜)。** プロンプト冒頭の `[現在のワークスペース]` のID値を使う。未所属の場合は空文字 `""` を明示的に渡す。workspace_id無しのリクエストは400エラーになる。
221
222
  - **再掲されたTodoを見たら、着手前に「既に完了していないか」を確認する。** 完了済みなら done に更新、未完なら続行。
222
223
  - 同一Todoが規定回数以上再掲されても未完了のままの場合、再掲は自動停止する。進展しないTodoはブロッカーとして起票するか手動で `cancelled` にすること。
223
224
 
@@ -0,0 +1,53 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Scaffold a new DB migration file with a timestamp prefix.
4
+ *
5
+ * Usage: npm run db:migration:new <snake_case_name>
6
+ */
7
+
8
+ const fs = require('fs')
9
+ const path = require('path')
10
+
11
+ const name = process.argv[2]
12
+ if (!name) {
13
+ console.error('Usage: npm run db:migration:new <snake_case_name>')
14
+ process.exit(1)
15
+ }
16
+
17
+ if (!/^[a-z][a-z0-9_]*$/.test(name)) {
18
+ console.error(`Invalid name "${name}". Use snake_case (lowercase letters, digits, underscores).`)
19
+ process.exit(1)
20
+ }
21
+
22
+ const now = new Date()
23
+ const pad = n => String(n).padStart(2, '0')
24
+ const ts = `${now.getUTCFullYear()}${pad(now.getUTCMonth() + 1)}${pad(now.getUTCDate())}${pad(now.getUTCHours())}${pad(now.getUTCMinutes())}${pad(now.getUTCSeconds())}`
25
+ const version = Number(ts)
26
+ const filename = `${ts}_${name}.js`
27
+ const filepath = path.join(__dirname, '..', 'core', 'db', 'migrations', filename)
28
+
29
+ if (fs.existsSync(filepath)) {
30
+ console.error(`File already exists: ${filepath}`)
31
+ process.exit(1)
32
+ }
33
+
34
+ const template = `/**
35
+ * TODO: describe what this migration does and why.
36
+ */
37
+
38
+ module.exports = {
39
+ version: ${version},
40
+ name: '${name}',
41
+
42
+ up(db, { hasColumn, tableExists }) {
43
+ // Add an idempotent guard so re-runs (e.g., after a partial failure) are safe:
44
+ // if (hasColumn(db, 'some_table', 'some_column')) return
45
+ //
46
+ // Then apply the change:
47
+ // db.exec(\`ALTER TABLE some_table ADD COLUMN some_column TEXT\`)
48
+ },
49
+ }
50
+ `
51
+
52
+ fs.writeFileSync(filepath, template)
53
+ console.log(`Created ${path.relative(process.cwd(), filepath)}`)
@@ -299,16 +299,21 @@ ${indexed}`
299
299
  return { success: true, carry_over: carryOver }
300
300
  })
301
301
 
302
- // POST /api/chat/end-of-day - Generate daily log + extract memories
302
+ // POST /api/chat/end-of-day - Generate daily log + extract memories for one workspace
303
303
  fastify.post('/api/chat/end-of-day', async (request, reply) => {
304
304
  if (!verifyToken(request)) {
305
305
  reply.code(401)
306
306
  return { success: false, error: 'Unauthorized' }
307
307
  }
308
308
 
309
- const { clear_session = false } = request.body || {}
309
+ const { workspace_id, clear_session = false } = request.body || {}
310
+ if (workspace_id !== '' && typeof workspace_id !== 'string') {
311
+ reply.code(400)
312
+ return { success: false, error: 'workspace_id is required' }
313
+ }
310
314
 
311
315
  const result = await runEndOfDay({
316
+ workspaceId: workspace_id,
312
317
  runQuickLlmCall,
313
318
  clearSession: clear_session,
314
319
  })
@@ -381,14 +386,14 @@ async function buildContextPrefix(message, context, sessionId, workspaceId) {
381
386
  '# メモリ詳細(IDを指定)',
382
387
  `curl -H "Authorization: Bearer $API_TOKEN" ${baseUrl}/api/memory/{id}`,
383
388
  '',
384
- '# デイリーログ検索',
385
- `curl -H "Authorization: Bearer $API_TOKEN" "${baseUrl}/api/daily-logs?search=キーワード"`,
389
+ '# デイリーログ検索(workspace_idは「現在のワークスペース」のIDを指定。未所属なら空文字)',
390
+ `curl -H "Authorization: Bearer $API_TOKEN" "${baseUrl}/api/daily-logs?workspace_id=現在のWSのID&search=キーワード"`,
386
391
  '',
387
- '# デイリーログ一覧',
388
- `curl -H "Authorization: Bearer $API_TOKEN" ${baseUrl}/api/daily-logs`,
392
+ '# デイリーログ一覧(現在のワークスペース)',
393
+ `curl -H "Authorization: Bearer $API_TOKEN" "${baseUrl}/api/daily-logs?workspace_id=現在のWSのID"`,
389
394
  '',
390
395
  '# 特定日のデイリーログ取得',
391
- `curl -H "Authorization: Bearer $API_TOKEN" ${baseUrl}/api/daily-logs/YYYY-MM-DD`,
396
+ `curl -H "Authorization: Bearer $API_TOKEN" "${baseUrl}/api/daily-logs/YYYY-MM-DD?workspace_id=現在のWSのID"`,
392
397
  '```',
393
398
  '',
394
399
  '参照すべきタイミング:',
@@ -429,10 +434,10 @@ async function buildContextPrefix(message, context, sessionId, workspaceId) {
429
434
  '',
430
435
  'ToDo APIの使い方:',
431
436
  '```bash',
432
- '# ToDo作成(session_idは後でセッションIDが判明してから設定)',
437
+ '# ToDo作成(workspace_idは必須。現在のワークスペースのIDを指定。未所属の場合は空文字 "")',
433
438
  `curl -X POST http://localhost:${port}/api/todos \\`,
434
439
  ' -H "Authorization: Bearer $API_TOKEN" -H "Content-Type: application/json" \\',
435
- ' -d \'{"title": "ステップの説明", "session_id": "SESSION_ID", "priority": "normal"}\'',
440
+ ' -d \'{"title": "ステップの説明", "workspace_id": "現在のWSのID", "session_id": "SESSION_ID", "priority": "normal"}\'',
436
441
  '',
437
442
  '# ToDo完了',
438
443
  `curl -X PUT http://localhost:${port}/api/todos/{id} \\`,
@@ -67,12 +67,14 @@ async function directiveRoutes(fastify) {
67
67
  const startedAt = new Date().toISOString()
68
68
  const logFile = logManager.getLogPath(effectiveExecutionId)
69
69
  const workflowName = context?.workflow_name || skill_name
70
+ const workspaceId = context?.workspace_id || ''
70
71
 
71
72
  await executionStore.save({
72
73
  id: effectiveExecutionId,
73
74
  skill_name,
74
75
  workflow_id: null,
75
76
  workflow_name: workflowName,
77
+ workspace_id: workspaceId,
76
78
  status: 'running',
77
79
  outcome: null,
78
80
  started_at: startedAt,
@@ -91,7 +93,8 @@ async function directiveRoutes(fastify) {
91
93
  id: effectiveExecutionId,
92
94
  name: workflowName,
93
95
  pipeline_skill_names: [skill_name],
94
- }, { skipExecutionReport: true })
96
+ workspace_id: workspaceId,
97
+ })
95
98
 
96
99
  console.log(`[Directive] Execution completed: ${skill_name} (success: ${result.execution_id ? 'yes' : 'no'})`)
97
100
  } catch (err) {
@@ -101,6 +104,7 @@ async function directiveRoutes(fastify) {
101
104
  skill_name,
102
105
  workflow_id: null,
103
106
  workflow_name: workflowName,
107
+ workspace_id: workspaceId,
104
108
  status: 'failed',
105
109
  outcome: 'failure',
106
110
  started_at: startedAt,