@geekbeer/minion 2.50.2 → 2.53.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.
@@ -11,6 +11,7 @@ const path = require('path')
11
11
  const { execSync } = require('child_process')
12
12
  const { config } = require('../config')
13
13
  const { IS_WINDOWS, buildExtendedPath } = require('./platform')
14
+ const variableStore = require('../stores/variable-store')
14
15
 
15
16
  const CACHE_TTL_MS = 300000 // 5 minutes
16
17
 
@@ -79,8 +80,19 @@ function checkTool(name) {
79
80
  const TOOL_NAMES = ['git', 'node', 'npx', 'claude', 'docker', 'tmux']
80
81
 
81
82
  /**
82
- * Get all capabilities (MCP servers + CLI tools), cached for 5 minutes.
83
- * @returns {{ mcp_servers: { name: string, configured: boolean }[], cli_tools: { name: string, available: boolean, version?: string }[] }}
83
+ * Get all available environment variable keys (variables + secrets).
84
+ * Returns deduplicated key names without values (secrets stay hidden).
85
+ * @returns {string[]}
86
+ */
87
+ function getEnvVarKeys() {
88
+ const varKeys = variableStore.listKeys('variables')
89
+ const secretKeys = variableStore.listKeys('secrets')
90
+ return [...new Set([...varKeys, ...secretKeys])]
91
+ }
92
+
93
+ /**
94
+ * Get all capabilities (MCP servers + CLI tools + env var keys), cached for 5 minutes.
95
+ * @returns {{ mcp_servers: { name: string, configured: boolean }[], cli_tools: { name: string, available: boolean, version?: string }[], env_var_keys: string[] }}
84
96
  */
85
97
  function getCapabilities() {
86
98
  const now = Date.now()
@@ -90,8 +102,9 @@ function getCapabilities() {
90
102
 
91
103
  const mcp_servers = getMcpServers()
92
104
  const cli_tools = TOOL_NAMES.map(name => checkTool(name))
105
+ const env_var_keys = getEnvVarKeys()
93
106
 
94
- cachedResult = { mcp_servers, cli_tools }
107
+ cachedResult = { mcp_servers, cli_tools, env_var_keys }
95
108
  cachedAt = now
96
109
  return cachedResult
97
110
  }
@@ -102,4 +115,14 @@ function clearCapabilityCache() {
102
115
  cachedAt = 0
103
116
  }
104
117
 
105
- module.exports = { getCapabilities, clearCapabilityCache }
118
+ /**
119
+ * Check arbitrary tool names on-demand (not cached).
120
+ * Used by readiness checks to verify skill-declared cli_tools.
121
+ * @param {string[]} names - Tool names to check
122
+ * @returns {{ name: string, available: boolean, version?: string }[]}
123
+ */
124
+ function checkTools(names) {
125
+ return names.map(name => checkTool(name))
126
+ }
127
+
128
+ module.exports = { getCapabilities, clearCapabilityCache, checkTools }
@@ -5,13 +5,14 @@
5
5
  * - GET /api/health - Health check
6
6
  * - GET /api/status - Get current status
7
7
  * - POST /api/status - Update status
8
+ * - POST /api/capabilities/check-tools - Check arbitrary CLI tools on-demand
8
9
  */
9
10
 
10
11
  const { version } = require('../../package.json')
11
12
  const { config, isHqConfigured } = require('../config')
12
13
  const { sendHeartbeat } = require('../api')
13
14
  const { getLlmServices, isLlmCommandConfigured } = require('../lib/llm-checker')
14
- const { getCapabilities } = require('../lib/capability-checker')
15
+ const { getCapabilities, checkTools } = require('../lib/capability-checker')
15
16
 
16
17
  function maskToken(token) {
17
18
  if (!token || token.length < 8) return token ? '***' : ''
@@ -105,6 +106,18 @@ async function healthRoutes(fastify) {
105
106
 
106
107
  return { success: true }
107
108
  })
109
+
110
+ // Check arbitrary CLI tools on-demand
111
+ fastify.post('/api/capabilities/check-tools', async (request, reply) => {
112
+ const { tools } = request.body || {}
113
+ if (!Array.isArray(tools) || tools.length === 0) {
114
+ reply.code(400)
115
+ return { error: 'tools must be a non-empty array of tool names' }
116
+ }
117
+ // Limit to 20 tools per request to avoid abuse
118
+ const limited = tools.slice(0, 20).filter(t => typeof t === 'string' && t.length > 0)
119
+ return { cli_tools: checkTools(limited) }
120
+ })
108
121
  }
109
122
 
110
123
  module.exports = {
@@ -58,17 +58,24 @@ async function writeSkillToLocal(name, { content, description, display_name, typ
58
58
  await fs.mkdir(skillDir, { recursive: true })
59
59
  await fs.mkdir(filesDir, { recursive: true })
60
60
 
61
- // Build frontmatter with all available metadata
62
- const frontmatterLines = [
63
- `name: ${name}`,
64
- display_name ? `display_name: ${display_name}` : null,
65
- type ? `type: ${type}` : null,
66
- `description: ${description || ''}`,
67
- ].filter(Boolean).join('\n')
61
+ // If content already has frontmatter, write as-is; otherwise build frontmatter
62
+ const hasFrontmatter = content.startsWith('---\n')
63
+ let fileContent
64
+ if (hasFrontmatter) {
65
+ fileContent = content
66
+ } else {
67
+ const frontmatterLines = [
68
+ `name: ${name}`,
69
+ display_name ? `display_name: ${display_name}` : null,
70
+ type ? `type: ${type}` : null,
71
+ `description: ${description || ''}`,
72
+ ].filter(Boolean).join('\n')
73
+ fileContent = `---\n${frontmatterLines}\n---\n\n${content}`
74
+ }
68
75
 
69
76
  await fs.writeFile(
70
77
  path.join(skillDir, 'SKILL.md'),
71
- `---\n${frontmatterLines}\n---\n\n${content}`,
78
+ fileContent,
72
79
  'utf-8'
73
80
  )
74
81
 
@@ -128,7 +135,7 @@ async function pushSkillToHQ(name) {
128
135
  name: metadata.name || name,
129
136
  display_name: metadata.display_name || metadata.name || name,
130
137
  description: metadata.description || '',
131
- content: body,
138
+ content: rawContent,
132
139
  type: metadata.type || 'workflow',
133
140
  files,
134
141
  }),
@@ -176,6 +176,7 @@ async function workflowRoutes(fastify, opts) {
176
176
  pipeline_skill_names: pipelineSkillNames,
177
177
  content: workflow.content || '',
178
178
  project_id: effectiveProjectId,
179
+ ...(workflow.pipeline_steps?.length > 0 && { pipeline_steps: workflow.pipeline_steps }),
179
180
  }),
180
181
  })
181
182
 
@@ -244,6 +245,10 @@ async function workflowRoutes(fastify, opts) {
244
245
  const updatedWorkflows = await workflowStore.upsertByName({
245
246
  name: workflow.name,
246
247
  pipeline_skill_names: workflow.pipeline_skill_names,
248
+ pipeline_steps: (workflow.pipeline || []).map(step => ({
249
+ assigned_role: step.assigned_role,
250
+ requires_review: step.requires_review || false,
251
+ })),
247
252
  content: workflow.content || '',
248
253
  project_id: workflow.project_id || null,
249
254
  })
@@ -82,7 +82,7 @@ async function findByName(name) {
82
82
  * Upsert a workflow by name.
83
83
  * If exists: updates definition only (preserves schedule/local state).
84
84
  * If new: creates with inactive schedule.
85
- * @param {object} workflowData - { name, pipeline_skill_names, content, project_id }
85
+ * @param {object} workflowData - { name, pipeline_skill_names, pipeline_steps, content, project_id }
86
86
  * @returns {Promise<Array>} Updated workflows array
87
87
  */
88
88
  async function upsertByName(workflowData) {
@@ -99,11 +99,15 @@ async function upsertByName(workflowData) {
99
99
  if (workflowData.project_id !== undefined) {
100
100
  workflows[index].project_id = workflowData.project_id
101
101
  }
102
+ if (workflowData.pipeline_steps !== undefined) {
103
+ workflows[index].pipeline_steps = workflowData.pipeline_steps
104
+ }
102
105
  } else {
103
106
  workflows.push({
104
107
  id: crypto.randomUUID(),
105
108
  name: workflowData.name,
106
109
  pipeline_skill_names: workflowData.pipeline_skill_names,
110
+ pipeline_steps: workflowData.pipeline_steps || [],
107
111
  content: workflowData.content || '',
108
112
  project_id: workflowData.project_id || null,
109
113
  cron_expression: '',
@@ -22,9 +22,8 @@ const path = require('path')
22
22
  const { verifyToken } = require('../../core/lib/auth')
23
23
  const { config } = require('../../core/config')
24
24
  const chatStore = require('../../core/stores/chat-store')
25
- const memoryStore = require('../../core/stores/memory-store')
26
- const dailyLogStore = require('../../core/stores/daily-log-store')
27
25
  const { runEndOfDay } = require('../../core/lib/end-of-day')
26
+ const { DATA_DIR } = require('../../core/lib/platform')
28
27
 
29
28
  /** @type {import('child_process').ChildProcess | null} */
30
29
  let activeChatChild = null
@@ -205,21 +204,24 @@ async function chatRoutes(fastify) {
205
204
  async function buildContextPrefix(message, context, sessionId) {
206
205
  const parts = []
207
206
 
208
- // Inject memory + daily logs on new sessions only (not on --resume)
207
+ // Tell the LLM where to find memory and daily logs (content is NOT injected)
209
208
  if (!sessionId) {
210
- try {
211
- const memorySnippet = await memoryStore.getContextSnippet(2000)
212
- const dailyLogSnippet = await dailyLogStore.getContextSnippet(3, 1500)
213
-
214
- if (memorySnippet) {
215
- parts.push('[ミニオンメモリ(長期記憶)]', memorySnippet, '')
216
- }
217
- if (dailyLogSnippet) {
218
- parts.push('[最近のデイリーログ]', dailyLogSnippet, '')
219
- }
220
- } catch (err) {
221
- console.error('[Chat] Failed to load memory/daily-log context:', err.message)
222
- }
209
+ const memoryDir = require('path').join(DATA_DIR, 'memory')
210
+ const dailyLogDir = require('path').join(DATA_DIR, 'daily-logs')
211
+ parts.push(
212
+ '[長期記憶・デイリーログについて]',
213
+ 'あなたには長期記憶(メモリ)とデイリーログがあります。内容は毎回読み込まれません。',
214
+ '必要なときだけ以下のファイルを読んで参照してください:',
215
+ `- メモリ索引: ${memoryDir}/MEMORY.md(概要一覧。詳細は個別の .md ファイルを読む)`,
216
+ `- デイリーログ: ${dailyLogDir}/YYYY-MM-DD.md(日付別の作業記録)`,
217
+ '',
218
+ '参照すべきタイミング:',
219
+ '- 過去の作業や決定事項を思い出す必要があるとき',
220
+ '- ユーザーの好みやフィードバックを確認したいとき',
221
+ '- 以前の作業の続きをするとき',
222
+ '- わからないことがあり、過去に記録した情報が役立ちそうなとき',
223
+ ''
224
+ )
223
225
  }
224
226
 
225
227
  if (context) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@geekbeer/minion",
3
- "version": "2.50.2",
3
+ "version": "2.53.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": {
@@ -18,9 +18,7 @@ const path = require('path')
18
18
  const { verifyToken } = require('../../core/lib/auth')
19
19
  const { config } = require('../../core/config')
20
20
  const chatStore = require('../../core/stores/chat-store')
21
- const memoryStore = require('../../core/stores/memory-store')
22
- const dailyLogStore = require('../../core/stores/daily-log-store')
23
- const { buildExtendedPath } = require('../../core/lib/platform')
21
+ const { buildExtendedPath, DATA_DIR } = require('../../core/lib/platform')
24
22
  const { runEndOfDay } = require('../../core/lib/end-of-day')
25
23
 
26
24
  let activeChatChild = null
@@ -168,21 +166,24 @@ async function chatRoutes(fastify) {
168
166
  async function buildContextPrefix(message, context, sessionId) {
169
167
  const parts = []
170
168
 
171
- // Inject memory + daily logs on new sessions only (not on --resume)
169
+ // Tell the LLM where to find memory and daily logs (content is NOT injected)
172
170
  if (!sessionId) {
173
- try {
174
- const memorySnippet = await memoryStore.getContextSnippet(2000)
175
- const dailyLogSnippet = await dailyLogStore.getContextSnippet(3, 1500)
176
-
177
- if (memorySnippet) {
178
- parts.push('[ミニオンメモリ(長期記憶)]', memorySnippet, '')
179
- }
180
- if (dailyLogSnippet) {
181
- parts.push('[最近のデイリーログ]', dailyLogSnippet, '')
182
- }
183
- } catch (err) {
184
- console.error('[Chat] Failed to load memory/daily-log context:', err.message)
185
- }
171
+ const memoryDir = require('path').join(DATA_DIR, 'memory')
172
+ const dailyLogDir = require('path').join(DATA_DIR, 'daily-logs')
173
+ parts.push(
174
+ '[長期記憶・デイリーログについて]',
175
+ 'あなたには長期記憶(メモリ)とデイリーログがあります。内容は毎回読み込まれません。',
176
+ '必要なときだけ以下のファイルを読んで参照してください:',
177
+ `- メモリ索引: ${memoryDir}/MEMORY.md(概要一覧。詳細は個別の .md ファイルを読む)`,
178
+ `- デイリーログ: ${dailyLogDir}/YYYY-MM-DD.md(日付別の作業記録)`,
179
+ '',
180
+ '参照すべきタイミング:',
181
+ '- 過去の作業や決定事項を思い出す必要があるとき',
182
+ '- ユーザーの好みやフィードバックを確認したいとき',
183
+ '- 以前の作業の続きをするとき',
184
+ '- わからないことがあり、過去に記録した情報が役立ちそうなとき',
185
+ ''
186
+ )
186
187
  }
187
188
 
188
189
  if (context) {