@geekbeer/minion 3.23.0 → 3.24.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 +5 -0
- package/core/db.js +70 -8
- package/core/routes/skills.js +35 -17
- package/docs/api-reference.md +10 -0
- package/package.json +1 -1
package/core/config.js
CHANGED
|
@@ -77,6 +77,11 @@ const config = {
|
|
|
77
77
|
MINION_ID: process.env.MINION_ID || '',
|
|
78
78
|
MINION_EMAIL: process.env.MINION_ID ? `m-${process.env.MINION_ID}@minion-agent.com` : '',
|
|
79
79
|
|
|
80
|
+
// Workspace targeting (optional)
|
|
81
|
+
// When set, skill push/fetch operations will target this workspace.
|
|
82
|
+
// If not set, HQ resolves the workspace from workspace_minions.
|
|
83
|
+
WORKSPACE_ID: process.env.WORKSPACE_ID || '',
|
|
84
|
+
|
|
80
85
|
// Server settings
|
|
81
86
|
AGENT_PORT: parseInt(process.env.AGENT_PORT, 10) || 8080,
|
|
82
87
|
|
package/core/db.js
CHANGED
|
@@ -267,7 +267,6 @@ function initSchema(db) {
|
|
|
267
267
|
CREATE INDEX IF NOT EXISTS idx_todos_status ON todos(status);
|
|
268
268
|
CREATE INDEX IF NOT EXISTS idx_todos_project ON todos(project_id);
|
|
269
269
|
CREATE INDEX IF NOT EXISTS idx_todos_priority ON todos(priority);
|
|
270
|
-
CREATE INDEX IF NOT EXISTS idx_todos_session ON todos(session_id);
|
|
271
270
|
|
|
272
271
|
-- ==================== emails ====================
|
|
273
272
|
CREATE TABLE IF NOT EXISTS emails (
|
|
@@ -451,9 +450,12 @@ function migrateSchema(db) {
|
|
|
451
450
|
|
|
452
451
|
console.log('[DB] Migration 3 complete: memories.project_id added')
|
|
453
452
|
} catch (err) {
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
453
|
+
if (hasColumn(db, 'memories', 'project_id')) {
|
|
454
|
+
console.warn(`[DB] Migration 3 skipped (column already exists): ${err.message}`)
|
|
455
|
+
try { db.exec("INSERT OR IGNORE INTO schema_version (version) VALUES (3)") } catch {}
|
|
456
|
+
} else {
|
|
457
|
+
console.error(`[DB] Migration 3 FAILED — memories.project_id not added: ${err.message}`)
|
|
458
|
+
}
|
|
457
459
|
}
|
|
458
460
|
}
|
|
459
461
|
|
|
@@ -472,8 +474,12 @@ function migrateSchema(db) {
|
|
|
472
474
|
|
|
473
475
|
console.log('[DB] Migration 4 complete: chat_sessions.workspace_id added')
|
|
474
476
|
} catch (err) {
|
|
475
|
-
|
|
476
|
-
|
|
477
|
+
if (hasColumn(db, 'chat_sessions', 'workspace_id')) {
|
|
478
|
+
console.warn(`[DB] Migration 4 skipped (column already exists): ${err.message}`)
|
|
479
|
+
try { db.exec("INSERT OR IGNORE INTO schema_version (version) VALUES (4)") } catch {}
|
|
480
|
+
} else {
|
|
481
|
+
console.error(`[DB] Migration 4 FAILED — chat_sessions.workspace_id not added: ${err.message}`)
|
|
482
|
+
}
|
|
477
483
|
}
|
|
478
484
|
}
|
|
479
485
|
|
|
@@ -492,10 +498,66 @@ function migrateSchema(db) {
|
|
|
492
498
|
|
|
493
499
|
console.log('[DB] Migration 5 complete: todos.session_id / injection_count added')
|
|
494
500
|
} catch (err) {
|
|
495
|
-
|
|
496
|
-
|
|
501
|
+
if (hasColumn(db, 'todos', 'session_id') && hasColumn(db, 'todos', 'injection_count')) {
|
|
502
|
+
console.warn(`[DB] Migration 5 skipped (columns already exist): ${err.message}`)
|
|
503
|
+
try { db.exec("INSERT OR IGNORE INTO schema_version (version) VALUES (5)") } catch {}
|
|
504
|
+
} else {
|
|
505
|
+
console.error(`[DB] Migration 5 FAILED — todos columns not added: ${err.message}`)
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
// Repair step: fix DBs where migrations were incorrectly marked as done
|
|
511
|
+
// but columns were never actually added (caused by the old catch-all error handling)
|
|
512
|
+
repairMissingColumns(db)
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
/**
|
|
516
|
+
* Check if a table has a specific column.
|
|
517
|
+
* @param {import('better-sqlite3').Database} db
|
|
518
|
+
* @param {string} table
|
|
519
|
+
* @param {string} column
|
|
520
|
+
* @returns {boolean}
|
|
521
|
+
*/
|
|
522
|
+
function hasColumn(db, table, column) {
|
|
523
|
+
const cols = db.prepare(`PRAGMA table_info(${table})`).all()
|
|
524
|
+
return cols.some(c => c.name === column)
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
/**
|
|
528
|
+
* Repair missing columns that should have been added by migrations.
|
|
529
|
+
* Handles DBs where migrations were marked as done but ALTER TABLE actually failed.
|
|
530
|
+
*/
|
|
531
|
+
function repairMissingColumns(db) {
|
|
532
|
+
const repairs = [
|
|
533
|
+
{ table: 'memories', column: 'project_id', sql: 'ALTER TABLE memories ADD COLUMN project_id TEXT DEFAULT NULL' },
|
|
534
|
+
{ table: 'chat_sessions', column: 'workspace_id', sql: 'ALTER TABLE chat_sessions ADD COLUMN workspace_id TEXT DEFAULT NULL' },
|
|
535
|
+
{ table: 'todos', column: 'session_id', sql: 'ALTER TABLE todos ADD COLUMN session_id TEXT' },
|
|
536
|
+
{ table: 'todos', column: 'injection_count', sql: 'ALTER TABLE todos ADD COLUMN injection_count INTEGER NOT NULL DEFAULT 0' },
|
|
537
|
+
]
|
|
538
|
+
|
|
539
|
+
for (const { table, column, sql } of repairs) {
|
|
540
|
+
if (!hasColumn(db, table, column)) {
|
|
541
|
+
try {
|
|
542
|
+
console.warn(`[DB] Repair: adding missing column ${table}.${column}`)
|
|
543
|
+
db.exec(sql)
|
|
544
|
+
console.log(`[DB] Repair: ${table}.${column} added successfully`)
|
|
545
|
+
} catch (err) {
|
|
546
|
+
console.error(`[DB] Repair FAILED for ${table}.${column}: ${err.message}`)
|
|
547
|
+
}
|
|
497
548
|
}
|
|
498
549
|
}
|
|
550
|
+
|
|
551
|
+
// Ensure indexes exist after repair
|
|
552
|
+
try {
|
|
553
|
+
db.exec(`
|
|
554
|
+
CREATE INDEX IF NOT EXISTS idx_memories_project_id ON memories(project_id);
|
|
555
|
+
CREATE INDEX IF NOT EXISTS idx_chat_sessions_workspace ON chat_sessions(workspace_id, updated_at DESC);
|
|
556
|
+
CREATE INDEX IF NOT EXISTS idx_todos_session ON todos(session_id);
|
|
557
|
+
`)
|
|
558
|
+
} catch (err) {
|
|
559
|
+
console.warn(`[DB] Repair index creation skipped: ${err.message}`)
|
|
560
|
+
}
|
|
499
561
|
}
|
|
500
562
|
|
|
501
563
|
/**
|
package/core/routes/skills.js
CHANGED
|
@@ -104,10 +104,12 @@ async function writeSkillToLocal(name, { content, description, display_name, typ
|
|
|
104
104
|
* Reusable by workflow push to auto-sync pipeline skills.
|
|
105
105
|
*
|
|
106
106
|
* @param {string} name - Skill slug (e.g. "execution-report")
|
|
107
|
+
* @param {object} [opts]
|
|
108
|
+
* @param {string} [opts.workspaceId] - Target workspace ID (overrides WORKSPACE_ID env var)
|
|
107
109
|
* @returns {Promise<object>} HQ response
|
|
108
110
|
* @throws {Error} If skill not found locally or HQ rejects
|
|
109
111
|
*/
|
|
110
|
-
async function pushSkillToHQ(name) {
|
|
112
|
+
async function pushSkillToHQ(name, opts = {}) {
|
|
111
113
|
// Use the Primary plugin's skill directory as the canonical read source.
|
|
112
114
|
// Skills are written identically to every enabled plugin dir, so reading
|
|
113
115
|
// from any one of them yields the same content.
|
|
@@ -140,16 +142,24 @@ async function pushSkillToHQ(name) {
|
|
|
140
142
|
}
|
|
141
143
|
}
|
|
142
144
|
|
|
145
|
+
const payload = {
|
|
146
|
+
name: metadata.name || name,
|
|
147
|
+
display_name: metadata.display_name || metadata.name || name,
|
|
148
|
+
description: metadata.description || '',
|
|
149
|
+
content: rawContent,
|
|
150
|
+
type: metadata.type || 'workflow',
|
|
151
|
+
files,
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Include workspace_id if specified or configured (for multi-workspace targeting)
|
|
155
|
+
const targetWorkspaceId = opts.workspaceId || config.WORKSPACE_ID
|
|
156
|
+
if (targetWorkspaceId) {
|
|
157
|
+
payload.workspace_id = targetWorkspaceId
|
|
158
|
+
}
|
|
159
|
+
|
|
143
160
|
return api.request('/skills', {
|
|
144
161
|
method: 'POST',
|
|
145
|
-
body: JSON.stringify(
|
|
146
|
-
name: metadata.name || name,
|
|
147
|
-
display_name: metadata.display_name || metadata.name || name,
|
|
148
|
-
description: metadata.description || '',
|
|
149
|
-
content: rawContent,
|
|
150
|
-
type: metadata.type || 'workflow',
|
|
151
|
-
files,
|
|
152
|
-
}),
|
|
162
|
+
body: JSON.stringify(payload),
|
|
153
163
|
})
|
|
154
164
|
}
|
|
155
165
|
|
|
@@ -204,6 +214,7 @@ async function skillRoutes(fastify, opts) {
|
|
|
204
214
|
})
|
|
205
215
|
|
|
206
216
|
// Push local skill to HQ
|
|
217
|
+
// Optional body: { workspace_id?: string } — overrides WORKSPACE_ID env var
|
|
207
218
|
fastify.post('/api/skills/push/:name', async (request, reply) => {
|
|
208
219
|
if (!verifyToken(request)) {
|
|
209
220
|
reply.code(401)
|
|
@@ -216,16 +227,17 @@ async function skillRoutes(fastify, opts) {
|
|
|
216
227
|
}
|
|
217
228
|
|
|
218
229
|
const { name } = request.params
|
|
230
|
+
const { workspace_id } = request.body || {}
|
|
219
231
|
|
|
220
232
|
if (!name || !/^[a-z0-9-]+$/.test(name)) {
|
|
221
233
|
reply.code(400)
|
|
222
234
|
return { success: false, error: 'Invalid skill name' }
|
|
223
235
|
}
|
|
224
236
|
|
|
225
|
-
console.log(`[Skills] Pushing skill to HQ: ${name}`)
|
|
237
|
+
console.log(`[Skills] Pushing skill to HQ: ${name}${workspace_id ? ` (workspace: ${workspace_id})` : ''}`)
|
|
226
238
|
|
|
227
239
|
try {
|
|
228
|
-
const result = await pushSkillToHQ(name)
|
|
240
|
+
const result = await pushSkillToHQ(name, { workspaceId: workspace_id })
|
|
229
241
|
console.log(`[Skills] Skill pushed to HQ: ${name}`)
|
|
230
242
|
return { success: true, ...result }
|
|
231
243
|
} catch (error) {
|
|
@@ -254,13 +266,17 @@ async function skillRoutes(fastify, opts) {
|
|
|
254
266
|
return { success: false, error: 'Invalid skill name' }
|
|
255
267
|
}
|
|
256
268
|
|
|
257
|
-
// Forward template vars query
|
|
269
|
+
// Forward template vars and workspace_id query params to HQ
|
|
258
270
|
const vars = request.query.vars || ''
|
|
259
|
-
const
|
|
260
|
-
|
|
271
|
+
const fetchWorkspaceId = request.query.workspace_id || config.WORKSPACE_ID || ''
|
|
272
|
+
const queryParams = new URLSearchParams()
|
|
273
|
+
if (vars) queryParams.set('vars', vars)
|
|
274
|
+
if (fetchWorkspaceId) queryParams.set('workspace_id', fetchWorkspaceId)
|
|
275
|
+
const queryString = queryParams.toString() ? `?${queryParams.toString()}` : ''
|
|
276
|
+
console.log(`[Skills] Fetching skill from HQ: ${name}${vars ? ' (with template vars)' : ''}${fetchWorkspaceId ? ` (workspace: ${fetchWorkspaceId})` : ''}`)
|
|
261
277
|
|
|
262
278
|
try {
|
|
263
|
-
// Fetch from HQ (with optional template variable expansion)
|
|
279
|
+
// Fetch from HQ (with optional template variable expansion and workspace scoping)
|
|
264
280
|
const skill = await api.request(`/skills/${encodeURIComponent(name)}${queryString}`)
|
|
265
281
|
|
|
266
282
|
// Write to local filesystem using shared helper
|
|
@@ -297,10 +313,12 @@ async function skillRoutes(fastify, opts) {
|
|
|
297
313
|
return { success: false, error: 'HQ not configured' }
|
|
298
314
|
}
|
|
299
315
|
|
|
300
|
-
|
|
316
|
+
const remoteWorkspaceId = request.query.workspace_id || config.WORKSPACE_ID || ''
|
|
317
|
+
const remoteQuery = remoteWorkspaceId ? `?workspace_id=${encodeURIComponent(remoteWorkspaceId)}` : ''
|
|
318
|
+
console.log(`[Skills] Listing remote skills from HQ${remoteWorkspaceId ? ` (workspace: ${remoteWorkspaceId})` : ''}`)
|
|
301
319
|
|
|
302
320
|
try {
|
|
303
|
-
const result = await api.request(
|
|
321
|
+
const result = await api.request(`/skills${remoteQuery}`)
|
|
304
322
|
return result
|
|
305
323
|
} catch (error) {
|
|
306
324
|
console.error(`[Skills] Failed to list remote skills: ${error.message}`)
|
package/docs/api-reference.md
CHANGED
|
@@ -27,6 +27,16 @@ Authentication: `Authorization: Bearer $API_TOKEN` header (except where noted).
|
|
|
27
27
|
| POST | `/api/skills/fetch/:name` | Fetch skill from HQ and deploy locally |
|
|
28
28
|
| GET | `/api/skills/remote` | List skills on HQ |
|
|
29
29
|
|
|
30
|
+
#### ワークスペーススコープ
|
|
31
|
+
|
|
32
|
+
スキルはワークスペース単位でスコープされる。`WORKSPACE_ID` 環境変数を設定するか、リクエストごとに `workspace_id` を指定することで、プッシュ先・取得元のワークスペースを制御できる。未指定の場合、HQがミニオンの所属ワークスペースから自動解決する。
|
|
33
|
+
|
|
34
|
+
| エンドポイント | パラメータ | 指定方法 |
|
|
35
|
+
|--------------|-----------|---------|
|
|
36
|
+
| `POST /api/skills/push/:name` | `workspace_id` | リクエストボディ |
|
|
37
|
+
| `POST /api/skills/fetch/:name` | `workspace_id` | クエリパラメータ |
|
|
38
|
+
| `GET /api/skills/remote` | `workspace_id` | クエリパラメータ |
|
|
39
|
+
|
|
30
40
|
### Workflows
|
|
31
41
|
|
|
32
42
|
| Method | Endpoint | Description |
|