@geekbeer/minion 2.70.2 → 3.4.7
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.js +297 -0
- package/core/lib/thread-watcher.js +3 -6
- package/core/routes/daily-logs.js +17 -11
- package/core/routes/memory.js +16 -10
- package/core/stores/chat-store.js +138 -76
- package/core/stores/daily-log-store.js +84 -105
- package/core/stores/execution-store.js +59 -84
- package/core/stores/memory-store.js +84 -177
- package/core/stores/routine-store.js +55 -72
- package/core/stores/workflow-store.js +52 -69
- package/docs/api-reference.md +6 -2
- package/linux/minion-cli.sh +203 -254
- package/linux/routes/chat.js +54 -7
- package/linux/server.js +3 -1
- package/package.json +3 -2
- package/roles/engineer.md +12 -0
- package/roles/pm.md +16 -0
- package/rules/core.md +66 -0
- package/win/lib/process-manager.js +115 -220
- package/win/minion-cli.ps1 +882 -675
- package/win/routes/chat.js +54 -7
- package/win/server.js +2 -0
- package/win/vendor/README.md +13 -0
- package/win/vendor/nssm.exe +0 -0
|
@@ -1,105 +1,86 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Routine Store
|
|
3
|
-
* Persists routine configurations to local
|
|
2
|
+
* Routine Store (SQLite)
|
|
3
|
+
* Persists routine configurations to local SQLite database.
|
|
4
4
|
* Allows minion to continue operating when HQ is offline.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
const
|
|
8
|
-
const path = require('path')
|
|
9
|
-
|
|
10
|
-
const { config } = require('../config')
|
|
11
|
-
const { DATA_DIR } = require('../lib/platform')
|
|
12
|
-
|
|
13
|
-
// Routine file location: DATA_DIR/routines.json (platform-aware)
|
|
14
|
-
// or ~/routines.json (fallback)
|
|
15
|
-
function getRoutineFilePath() {
|
|
16
|
-
try {
|
|
17
|
-
require('fs').accessSync(DATA_DIR)
|
|
18
|
-
return path.join(DATA_DIR, 'routines.json')
|
|
19
|
-
} catch {
|
|
20
|
-
return path.join(config.HOME_DIR, 'routines.json')
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
const ROUTINE_FILE = getRoutineFilePath()
|
|
7
|
+
const { getDb } = require('../db')
|
|
25
8
|
|
|
26
9
|
/**
|
|
27
|
-
* Load routines
|
|
28
|
-
* @returns {
|
|
10
|
+
* Load all routines
|
|
11
|
+
* @returns {Array} Array of routine objects
|
|
29
12
|
*/
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
} catch (err) {
|
|
35
|
-
if (err.code === 'ENOENT') {
|
|
36
|
-
return []
|
|
37
|
-
}
|
|
38
|
-
console.error(`[RoutineStore] Failed to load routines: ${err.message}`)
|
|
39
|
-
return []
|
|
40
|
-
}
|
|
13
|
+
function load() {
|
|
14
|
+
const db = getDb()
|
|
15
|
+
const rows = db.prepare('SELECT data FROM routines ORDER BY name').all()
|
|
16
|
+
return rows.map(r => JSON.parse(r.data))
|
|
41
17
|
}
|
|
42
18
|
|
|
43
19
|
/**
|
|
44
|
-
* Save routines
|
|
20
|
+
* Save routines (replace all)
|
|
45
21
|
* @param {Array} routines - Array of routine objects
|
|
46
22
|
*/
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
23
|
+
function save(routines) {
|
|
24
|
+
const db = getDb()
|
|
25
|
+
const tx = db.transaction(() => {
|
|
26
|
+
db.prepare('DELETE FROM routines').run()
|
|
27
|
+
const insert = db.prepare('INSERT INTO routines (id, name, data) VALUES (?, ?, ?)')
|
|
28
|
+
for (const r of routines) {
|
|
29
|
+
insert.run(r.id, r.name, JSON.stringify(r))
|
|
30
|
+
}
|
|
31
|
+
})
|
|
32
|
+
tx()
|
|
33
|
+
console.log(`[RoutineStore] Saved ${routines.length} routines`)
|
|
55
34
|
}
|
|
56
35
|
|
|
57
36
|
/**
|
|
58
37
|
* Update last_run for a specific routine
|
|
59
38
|
* @param {string} routineId - Routine UUID
|
|
60
39
|
*/
|
|
61
|
-
|
|
62
|
-
const
|
|
63
|
-
const
|
|
64
|
-
if (
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
40
|
+
function updateLastRun(routineId) {
|
|
41
|
+
const db = getDb()
|
|
42
|
+
const row = db.prepare('SELECT data FROM routines WHERE id = ?').get(String(routineId))
|
|
43
|
+
if (!row) return
|
|
44
|
+
|
|
45
|
+
const routine = JSON.parse(row.data)
|
|
46
|
+
routine.last_run = new Date().toISOString()
|
|
47
|
+
db.prepare('UPDATE routines SET data = ? WHERE id = ?').run(JSON.stringify(routine), String(routineId))
|
|
68
48
|
}
|
|
69
49
|
|
|
70
50
|
/**
|
|
71
51
|
* Find a routine by name
|
|
72
52
|
* @param {string} name - Routine name
|
|
73
|
-
* @returns {
|
|
53
|
+
* @returns {object|null} Routine object or null
|
|
74
54
|
*/
|
|
75
|
-
|
|
76
|
-
const
|
|
77
|
-
|
|
55
|
+
function findByName(name) {
|
|
56
|
+
const db = getDb()
|
|
57
|
+
const row = db.prepare('SELECT data FROM routines WHERE name = ?').get(name)
|
|
58
|
+
return row ? JSON.parse(row.data) : null
|
|
78
59
|
}
|
|
79
60
|
|
|
80
61
|
/**
|
|
81
62
|
* Upsert a routine from HQ data.
|
|
82
63
|
* Upserts by ID (routines come from HQ with UUIDs).
|
|
83
64
|
* @param {object} routineData - { id, name, pipeline_skill_names, content, is_active, cron_expression, last_run, next_run }
|
|
84
|
-
* @returns {
|
|
65
|
+
* @returns {Array} Updated routines array
|
|
85
66
|
*/
|
|
86
|
-
|
|
87
|
-
const
|
|
88
|
-
const
|
|
67
|
+
function upsertFromHQ(routineData) {
|
|
68
|
+
const db = getDb()
|
|
69
|
+
const existing = db.prepare('SELECT data FROM routines WHERE id = ?').get(String(routineData.id))
|
|
89
70
|
|
|
90
|
-
if (
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
71
|
+
if (existing) {
|
|
72
|
+
const routine = JSON.parse(existing.data)
|
|
73
|
+
routine.name = routineData.name
|
|
74
|
+
routine.pipeline_skill_names = routineData.pipeline_skill_names
|
|
75
|
+
routine.content = routineData.content || ''
|
|
76
|
+
routine.context = routineData.context || null
|
|
77
|
+
routine.is_active = routineData.is_active
|
|
78
|
+
routine.cron_expression = routineData.cron_expression || ''
|
|
79
|
+
db.prepare('UPDATE routines SET name = ?, data = ? WHERE id = ?').run(
|
|
80
|
+
routine.name, JSON.stringify(routine), String(routineData.id)
|
|
81
|
+
)
|
|
101
82
|
} else {
|
|
102
|
-
|
|
83
|
+
const routine = {
|
|
103
84
|
id: routineData.id,
|
|
104
85
|
name: routineData.name,
|
|
105
86
|
pipeline_skill_names: routineData.pipeline_skill_names || [],
|
|
@@ -109,11 +90,13 @@ async function upsertFromHQ(routineData) {
|
|
|
109
90
|
cron_expression: routineData.cron_expression || '',
|
|
110
91
|
last_run: routineData.last_run || null,
|
|
111
92
|
next_run: routineData.next_run || null,
|
|
112
|
-
}
|
|
93
|
+
}
|
|
94
|
+
db.prepare('INSERT INTO routines (id, name, data) VALUES (?, ?, ?)').run(
|
|
95
|
+
String(routine.id), routine.name, JSON.stringify(routine)
|
|
96
|
+
)
|
|
113
97
|
}
|
|
114
98
|
|
|
115
|
-
|
|
116
|
-
return routines
|
|
99
|
+
return load()
|
|
117
100
|
}
|
|
118
101
|
|
|
119
102
|
module.exports = { load, save, updateLastRun, findByName, upsertFromHQ }
|
|
@@ -1,81 +1,62 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Workflow Store
|
|
3
|
-
* Persists workflow configurations to local
|
|
2
|
+
* Workflow Store (SQLite)
|
|
3
|
+
* Persists workflow configurations to local SQLite database.
|
|
4
4
|
* Allows minion to continue operating when HQ is offline.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
const
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
const { config } = require('../config')
|
|
11
|
-
const { DATA_DIR } = require('../lib/platform')
|
|
12
|
-
|
|
13
|
-
// Workflow file location: DATA_DIR/workflows.json (platform-aware)
|
|
14
|
-
// or ~/workflows.json (fallback)
|
|
15
|
-
function getWorkflowFilePath() {
|
|
16
|
-
try {
|
|
17
|
-
require('fs').accessSync(DATA_DIR)
|
|
18
|
-
return path.join(DATA_DIR, 'workflows.json')
|
|
19
|
-
} catch {
|
|
20
|
-
return path.join(config.HOME_DIR, 'workflows.json')
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
const WORKFLOW_FILE = getWorkflowFilePath()
|
|
7
|
+
const crypto = require('crypto')
|
|
8
|
+
const { getDb } = require('../db')
|
|
25
9
|
|
|
26
10
|
/**
|
|
27
|
-
* Load workflows
|
|
28
|
-
* @returns {
|
|
11
|
+
* Load all workflows
|
|
12
|
+
* @returns {Array} Array of workflow objects
|
|
29
13
|
*/
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
} catch (err) {
|
|
35
|
-
if (err.code === 'ENOENT') {
|
|
36
|
-
// File doesn't exist yet — normal on first run
|
|
37
|
-
return []
|
|
38
|
-
}
|
|
39
|
-
console.error(`[WorkflowStore] Failed to load workflows: ${err.message}`)
|
|
40
|
-
return []
|
|
41
|
-
}
|
|
14
|
+
function load() {
|
|
15
|
+
const db = getDb()
|
|
16
|
+
const rows = db.prepare('SELECT data FROM workflows ORDER BY name').all()
|
|
17
|
+
return rows.map(r => JSON.parse(r.data))
|
|
42
18
|
}
|
|
43
19
|
|
|
44
20
|
/**
|
|
45
|
-
* Save workflows
|
|
21
|
+
* Save workflows (replace all)
|
|
46
22
|
* @param {Array} workflows - Array of workflow objects
|
|
47
23
|
*/
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
24
|
+
function save(workflows) {
|
|
25
|
+
const db = getDb()
|
|
26
|
+
const tx = db.transaction(() => {
|
|
27
|
+
db.prepare('DELETE FROM workflows').run()
|
|
28
|
+
const insert = db.prepare('INSERT INTO workflows (id, name, data) VALUES (?, ?, ?)')
|
|
29
|
+
for (const w of workflows) {
|
|
30
|
+
insert.run(w.id, w.name, JSON.stringify(w))
|
|
31
|
+
}
|
|
32
|
+
})
|
|
33
|
+
tx()
|
|
34
|
+
console.log(`[WorkflowStore] Saved ${workflows.length} workflows`)
|
|
56
35
|
}
|
|
57
36
|
|
|
58
37
|
/**
|
|
59
38
|
* Update last_run for a specific workflow
|
|
60
39
|
* @param {string} workflowId - Workflow UUID
|
|
61
40
|
*/
|
|
62
|
-
|
|
63
|
-
const
|
|
64
|
-
const
|
|
65
|
-
if (
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
41
|
+
function updateLastRun(workflowId) {
|
|
42
|
+
const db = getDb()
|
|
43
|
+
const row = db.prepare('SELECT data FROM workflows WHERE id = ?').get(workflowId)
|
|
44
|
+
if (!row) return
|
|
45
|
+
|
|
46
|
+
const workflow = JSON.parse(row.data)
|
|
47
|
+
workflow.last_run = new Date().toISOString()
|
|
48
|
+
db.prepare('UPDATE workflows SET data = ? WHERE id = ?').run(JSON.stringify(workflow), workflowId)
|
|
69
49
|
}
|
|
70
50
|
|
|
71
51
|
/**
|
|
72
52
|
* Find a workflow by name
|
|
73
53
|
* @param {string} name - Workflow name (slug)
|
|
74
|
-
* @returns {
|
|
54
|
+
* @returns {object|null} Workflow object or null
|
|
75
55
|
*/
|
|
76
|
-
|
|
77
|
-
const
|
|
78
|
-
|
|
56
|
+
function findByName(name) {
|
|
57
|
+
const db = getDb()
|
|
58
|
+
const row = db.prepare('SELECT data FROM workflows WHERE name = ?').get(name)
|
|
59
|
+
return row ? JSON.parse(row.data) : null
|
|
79
60
|
}
|
|
80
61
|
|
|
81
62
|
/**
|
|
@@ -83,27 +64,27 @@ async function findByName(name) {
|
|
|
83
64
|
* If exists: updates definition only (preserves schedule/local state).
|
|
84
65
|
* If new: creates with inactive schedule.
|
|
85
66
|
* @param {object} workflowData - { name, pipeline_skill_names, pipeline_steps, content, project_id }
|
|
86
|
-
* @returns {
|
|
67
|
+
* @returns {Array} Updated workflows array
|
|
87
68
|
*/
|
|
88
|
-
|
|
89
|
-
const
|
|
90
|
-
const workflows =
|
|
91
|
-
const index = workflows.findIndex(w => w.name === workflowData.name)
|
|
69
|
+
function upsertByName(workflowData) {
|
|
70
|
+
const db = getDb()
|
|
71
|
+
const existing = db.prepare('SELECT data FROM workflows WHERE name = ?').get(workflowData.name)
|
|
92
72
|
|
|
93
|
-
if (
|
|
94
|
-
|
|
95
|
-
|
|
73
|
+
if (existing) {
|
|
74
|
+
const workflow = JSON.parse(existing.data)
|
|
75
|
+
workflow.pipeline_skill_names = workflowData.pipeline_skill_names
|
|
96
76
|
if (workflowData.content !== undefined) {
|
|
97
|
-
|
|
77
|
+
workflow.content = workflowData.content
|
|
98
78
|
}
|
|
99
79
|
if (workflowData.project_id !== undefined) {
|
|
100
|
-
|
|
80
|
+
workflow.project_id = workflowData.project_id
|
|
101
81
|
}
|
|
102
82
|
if (workflowData.pipeline_steps !== undefined) {
|
|
103
|
-
|
|
83
|
+
workflow.pipeline_steps = workflowData.pipeline_steps
|
|
104
84
|
}
|
|
85
|
+
db.prepare('UPDATE workflows SET data = ? WHERE id = ?').run(JSON.stringify(workflow), workflow.id)
|
|
105
86
|
} else {
|
|
106
|
-
|
|
87
|
+
const workflow = {
|
|
107
88
|
id: crypto.randomUUID(),
|
|
108
89
|
name: workflowData.name,
|
|
109
90
|
pipeline_skill_names: workflowData.pipeline_skill_names,
|
|
@@ -113,11 +94,13 @@ async function upsertByName(workflowData) {
|
|
|
113
94
|
cron_expression: '',
|
|
114
95
|
is_active: false,
|
|
115
96
|
last_run: null,
|
|
116
|
-
}
|
|
97
|
+
}
|
|
98
|
+
db.prepare('INSERT INTO workflows (id, name, data) VALUES (?, ?, ?)').run(
|
|
99
|
+
workflow.id, workflow.name, JSON.stringify(workflow)
|
|
100
|
+
)
|
|
117
101
|
}
|
|
118
102
|
|
|
119
|
-
|
|
120
|
-
return workflows
|
|
103
|
+
return load()
|
|
121
104
|
}
|
|
122
105
|
|
|
123
106
|
module.exports = { load, save, updateLastRun, findByName, upsertByName }
|
package/docs/api-reference.md
CHANGED
|
@@ -77,12 +77,14 @@ Files are stored in `~/files/`. Max upload size: 50MB.
|
|
|
77
77
|
|
|
78
78
|
### Memory (Long-term Knowledge)
|
|
79
79
|
|
|
80
|
-
Persistent memory entries stored
|
|
80
|
+
Persistent memory entries stored in SQLite (`$DATA_DIR/minion.db`).
|
|
81
81
|
Categories: `user` (user preferences), `feedback` (feedback), `project` (project info), `reference` (references).
|
|
82
|
+
Full-text search supported via FTS5.
|
|
82
83
|
|
|
83
84
|
| Method | Endpoint | Description |
|
|
84
85
|
|--------|----------|-------------|
|
|
85
86
|
| GET | `/api/memory` | List all memory entries (id, title, category, excerpt, timestamps) |
|
|
87
|
+
| GET | `/api/memory?search=keyword` | Full-text search on title and content (FTS5) |
|
|
86
88
|
| GET | `/api/memory/:id` | Get full memory entry |
|
|
87
89
|
| POST | `/api/memory` | Create memory entry. Body: `{title, category, content}` |
|
|
88
90
|
| PUT | `/api/memory/:id` | Update memory entry. Body: `{title?, category?, content?}` |
|
|
@@ -116,12 +118,14 @@ Response (list):
|
|
|
116
118
|
|
|
117
119
|
### Daily Logs (Short-term Memory)
|
|
118
120
|
|
|
119
|
-
Daily conversation summaries stored
|
|
121
|
+
Daily conversation summaries stored in SQLite (`$DATA_DIR/minion.db`).
|
|
120
122
|
Generated via end-of-day processing or manual creation.
|
|
123
|
+
Full-text search supported via FTS5.
|
|
121
124
|
|
|
122
125
|
| Method | Endpoint | Description |
|
|
123
126
|
|--------|----------|-------------|
|
|
124
127
|
| GET | `/api/daily-logs` | List all logs (date + size, newest first) |
|
|
128
|
+
| GET | `/api/daily-logs?search=keyword` | Full-text search on log content (FTS5) |
|
|
125
129
|
| POST | `/api/daily-logs` | Create a daily log. Body: `{date, content}` |
|
|
126
130
|
| GET | `/api/daily-logs/:date` | Get a specific day's log content |
|
|
127
131
|
| PUT | `/api/daily-logs/:date` | Update a daily log. Body: `{content}` |
|