@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.
- package/core/db/helpers.js +18 -0
- package/core/db/index.js +146 -0
- package/core/db/migrations/000_initial_schema.js +157 -0
- package/core/db/migrations/001_fts_trigram.js +78 -0
- package/core/db/migrations/002_emails_fts.js +41 -0
- package/core/db/migrations/003_memories_project_id.js +17 -0
- package/core/db/migrations/004_chat_sessions_workspace.js +18 -0
- package/core/db/migrations/005_todos_session_injection.js +19 -0
- package/core/db/migrations/006_daily_logs_workspace.js +69 -0
- package/core/db/migrations/007_workspace_scoping.js +29 -0
- package/core/db/migrations/008_todos_workspace.js +22 -0
- package/core/db/migrations/index.js +41 -0
- package/core/lib/config-warnings.js +16 -8
- package/core/lib/end-of-day.js +30 -14
- package/core/lib/reflection-scheduler.js +23 -9
- package/core/lib/thread-watcher.js +3 -0
- package/core/routes/daily-logs.js +64 -27
- package/core/routes/routines.js +6 -2
- package/core/routes/skills.js +10 -5
- package/core/routes/todos.js +20 -7
- package/core/routes/workflows.js +17 -7
- package/core/stores/daily-log-store.js +61 -30
- package/core/stores/execution-store.js +40 -18
- package/core/stores/routine-store.js +32 -14
- package/core/stores/todo-store.js +37 -10
- package/core/stores/workflow-store.js +34 -13
- package/docs/api-reference.md +68 -27
- package/linux/routes/chat.js +14 -9
- package/linux/routes/directives.js +5 -4
- package/linux/routine-runner.js +42 -7
- package/linux/workflow-runner.js +3 -1
- package/package.json +4 -2
- package/roles/engineer.md +0 -1
- package/rules/core.md +1 -0
- package/scripts/new-migration.js +53 -0
- package/win/routes/chat.js +14 -9
- package/win/routes/directives.js +5 -1
- package/win/routine-runner.js +40 -4
- package/win/workflow-runner.js +2 -0
- package/core/db.js +0 -583
package/docs/api-reference.md
CHANGED
|
@@ -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
|
|
60
|
-
| POST | `/api/workflows` | Receive/upsert workflows (from HQ
|
|
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
|
-
|
|
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
|
|
186
|
-
| GET | `/api/daily-logs?search=keyword` |
|
|
187
|
-
| POST | `/api/daily-logs` | Create a daily log. Body: `{date, content}` |
|
|
188
|
-
| GET | `/api/daily-logs/:date
|
|
189
|
-
| PUT | `/api/daily-logs/:date` | Update a daily log. Body: `{content}` |
|
|
190
|
-
| DELETE | `/api/daily-logs/:date
|
|
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:
|
|
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
|
-
#
|
|
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
|
|
340
|
+
### LLM Plugins
|
|
302
341
|
|
|
303
|
-
プラグイン方式の LLM
|
|
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"
|
|
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"
|
|
1751
|
+
"pipeline_skill_names": ["skill-1"],
|
|
1711
1752
|
"content": "...",
|
|
1712
1753
|
"is_active": true,
|
|
1713
1754
|
"cron_expression": "0 9 * * 1-5",
|
package/linux/routes/chat.js
CHANGED
|
@@ -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作成(
|
|
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
|
-
|
|
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,
|
package/linux/routine-runner.js
CHANGED
|
@@ -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
|
-
* -
|
|
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
|
|
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
|
|
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(' → ')}
|
|
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
|
|
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
|
|
package/linux/workflow-runner.js
CHANGED
|
@@ -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
|
|
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.
|
|
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
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)}`)
|
package/win/routes/chat.js
CHANGED
|
@@ -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作成(
|
|
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} \\`,
|
package/win/routes/directives.js
CHANGED
|
@@ -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
|
-
|
|
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,
|