@geekbeer/minion 2.44.0 → 2.48.1

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.
@@ -8,6 +8,7 @@
8
8
  */
9
9
 
10
10
  const { execSync } = require('child_process')
11
+ const fs = require('fs')
11
12
  const path = require('path')
12
13
  const os = require('os')
13
14
 
@@ -18,7 +19,6 @@ const os = require('os')
18
19
  */
19
20
  function detectProcessManager() {
20
21
  // Check for user-process mode (PID file or start-agent.ps1 exists)
21
- const fs = require('fs')
22
22
  const dataDir = path.join(os.homedir(), '.minion')
23
23
  const startScript = path.join(dataDir, 'start-agent.ps1')
24
24
  if (fs.existsSync(startScript)) {
@@ -40,6 +40,56 @@ function detectProcessManager() {
40
40
  return 'user-process'
41
41
  }
42
42
 
43
+ /**
44
+ * Generate a temporary PowerShell script that:
45
+ * 1. Stops the running agent process (releasing file locks)
46
+ * 2. Runs npm install -g
47
+ * 3. Restarts the agent
48
+ *
49
+ * This is necessary because node-pty's conpty.node DLL is locked by the
50
+ * running process, causing EBUSY errors if npm tries to overwrite it in-place.
51
+ *
52
+ * @param {string} npmInstallCmd - The npm install command to run
53
+ * @param {string} stopCmd - Command/script block to stop the agent
54
+ * @param {string} startCmd - Command/script block to start the agent
55
+ * @returns {string} - Shell command that writes and launches the update script
56
+ */
57
+ function buildUpdateScript(npmInstallCmd, stopCmd, startCmd) {
58
+ const dataDir = path.join(os.homedir(), '.minion')
59
+ const scriptPath = path.join(dataDir, 'update-agent.ps1')
60
+ const logPath = path.join(dataDir, 'update-agent.log')
61
+
62
+ // PowerShell script content: stop → install → start, with logging
63
+ const ps1 = [
64
+ `$ErrorActionPreference = 'Stop'`,
65
+ `$logFile = '${logPath.replace(/\\/g, '\\\\')}'`,
66
+ `function Log($msg) { "$(Get-Date -Format o) $msg" | Out-File -Append $logFile }`,
67
+ `Log 'Update started'`,
68
+ `try {`,
69
+ ` Log 'Stopping agent...'`,
70
+ ` ${stopCmd}`,
71
+ ` Start-Sleep -Seconds 3`,
72
+ ` Log 'Installing package...'`,
73
+ ` $out = & cmd /c "${npmInstallCmd} 2>&1"`,
74
+ ` Log "npm output: $out"`,
75
+ ` if ($LASTEXITCODE -ne 0) { throw "npm install failed (exit code $LASTEXITCODE)" }`,
76
+ ` Log 'Starting agent...'`,
77
+ ` ${startCmd}`,
78
+ ` Log 'Update completed successfully'`,
79
+ `} catch {`,
80
+ ` Log "Update failed: $_"`,
81
+ ` Log 'Attempting to restart agent anyway...'`,
82
+ ` ${startCmd}`,
83
+ `}`,
84
+ ].join('\n')
85
+
86
+ // Write the script to disk and launch it detached
87
+ try { fs.mkdirSync(dataDir, { recursive: true }) } catch { /* exists */ }
88
+ fs.writeFileSync(scriptPath, ps1, 'utf-8')
89
+
90
+ return `powershell -ExecutionPolicy Bypass -Command "Start-Process powershell -ArgumentList '-ExecutionPolicy Bypass -WindowStyle Hidden -File \\"${scriptPath}\\"' -WindowStyle Hidden"`
91
+ }
92
+
43
93
  /**
44
94
  * Build allowed commands for the detected process manager.
45
95
  * @param {string} procMgr - Process manager type
@@ -53,19 +103,30 @@ function buildAllowedCommands(procMgr) {
53
103
  const pidFile = path.join(dataDir, 'minion-agent.pid')
54
104
  const startScript = path.join(dataDir, 'start-agent.ps1')
55
105
 
106
+ const stopBlock = `$pid = Get-Content '${pidFile}' -ErrorAction SilentlyContinue; if ($pid) { Get-CimInstance Win32_Process -Filter \\"ParentProcessId = $pid\\" -ErrorAction SilentlyContinue | ForEach-Object { Stop-Process -Id $_.ProcessId -Force -ErrorAction SilentlyContinue }; Stop-Process -Id $pid -Force -ErrorAction SilentlyContinue }`
107
+ const startBlock = `Start-Process powershell -ArgumentList '-ExecutionPolicy Bypass -WindowStyle Hidden -File \\"${startScript}\\"' -WindowStyle Hidden`
108
+
56
109
  commands['restart-agent'] = {
57
110
  description: 'Restart the minion agent process',
58
- command: `powershell -Command "$pid = Get-Content '${pidFile}' -ErrorAction SilentlyContinue; if ($pid) { Get-CimInstance Win32_Process -Filter \\"ParentProcessId = $pid\\" -ErrorAction SilentlyContinue | ForEach-Object { Stop-Process -Id $_.ProcessId -Force -ErrorAction SilentlyContinue }; Stop-Process -Id $pid -Force -ErrorAction SilentlyContinue }; Start-Sleep -Seconds 2; Start-Process powershell -ArgumentList '-ExecutionPolicy Bypass -WindowStyle Hidden -File \\"${startScript}\\"' -WindowStyle Hidden"`,
111
+ command: `powershell -Command "${stopBlock}; Start-Sleep -Seconds 2; ${startBlock}"`,
59
112
  deferred: true,
60
113
  }
61
114
  commands['update-agent'] = {
62
115
  description: 'Update @geekbeer/minion to latest version and restart',
63
- command: `npm install -g @geekbeer/minion@latest && powershell -Command "$pid = Get-Content '${pidFile}' -ErrorAction SilentlyContinue; if ($pid) { Get-CimInstance Win32_Process -Filter \\"ParentProcessId = $pid\\" -ErrorAction SilentlyContinue | ForEach-Object { Stop-Process -Id $_.ProcessId -Force -ErrorAction SilentlyContinue }; Stop-Process -Id $pid -Force -ErrorAction SilentlyContinue }; Start-Sleep -Seconds 2; Start-Process powershell -ArgumentList '-ExecutionPolicy Bypass -WindowStyle Hidden -File \\"${startScript}\\"' -WindowStyle Hidden"`,
116
+ command: buildUpdateScript(
117
+ 'npm install -g @geekbeer/minion@latest',
118
+ stopBlock,
119
+ startBlock,
120
+ ),
64
121
  deferred: true,
65
122
  }
66
123
  commands['update-agent-dev'] = {
67
124
  description: 'Update @geekbeer/minion from Verdaccio (dev) and restart',
68
- command: `npm install -g @geekbeer/minion@latest --registry http://verdaccio:4873 && powershell -Command "$pid = Get-Content '${pidFile}' -ErrorAction SilentlyContinue; if ($pid) { Get-CimInstance Win32_Process -Filter \\"ParentProcessId = $pid\\" -ErrorAction SilentlyContinue | ForEach-Object { Stop-Process -Id $_.ProcessId -Force -ErrorAction SilentlyContinue }; Stop-Process -Id $pid -Force -ErrorAction SilentlyContinue }; Start-Sleep -Seconds 2; Start-Process powershell -ArgumentList '-ExecutionPolicy Bypass -WindowStyle Hidden -File \\"${startScript}\\"' -WindowStyle Hidden"`,
125
+ command: buildUpdateScript(
126
+ 'npm install -g @geekbeer/minion@latest --registry http://verdaccio:4873',
127
+ stopBlock,
128
+ startBlock,
129
+ ),
69
130
  deferred: true,
70
131
  }
71
132
  commands['status-services'] = {
@@ -80,12 +141,20 @@ function buildAllowedCommands(procMgr) {
80
141
  }
81
142
  commands['update-agent'] = {
82
143
  description: 'Update @geekbeer/minion to latest version and restart',
83
- command: 'npm install -g @geekbeer/minion@latest && nssm restart minion-agent',
144
+ command: buildUpdateScript(
145
+ 'npm install -g @geekbeer/minion@latest',
146
+ 'nssm stop minion-agent',
147
+ 'nssm start minion-agent',
148
+ ),
84
149
  deferred: true,
85
150
  }
86
151
  commands['update-agent-dev'] = {
87
152
  description: 'Update @geekbeer/minion from Verdaccio (dev) and restart',
88
- command: 'npm install -g @geekbeer/minion@latest --registry http://verdaccio:4873 && nssm restart minion-agent',
153
+ command: buildUpdateScript(
154
+ 'npm install -g @geekbeer/minion@latest --registry http://verdaccio:4873',
155
+ 'nssm stop minion-agent',
156
+ 'nssm start minion-agent',
157
+ ),
89
158
  deferred: true,
90
159
  }
91
160
  commands['restart-display'] = {
@@ -104,12 +173,20 @@ function buildAllowedCommands(procMgr) {
104
173
  }
105
174
  commands['update-agent'] = {
106
175
  description: 'Update @geekbeer/minion to latest version and restart',
107
- command: 'npm install -g @geekbeer/minion@latest && net stop minion-agent & net start minion-agent',
176
+ command: buildUpdateScript(
177
+ 'npm install -g @geekbeer/minion@latest',
178
+ 'net stop minion-agent',
179
+ 'net start minion-agent',
180
+ ),
108
181
  deferred: true,
109
182
  }
110
183
  commands['update-agent-dev'] = {
111
184
  description: 'Update @geekbeer/minion from Verdaccio (dev) and restart',
112
- command: 'npm install -g @geekbeer/minion@latest --registry http://verdaccio:4873 && net stop minion-agent & net start minion-agent',
185
+ command: buildUpdateScript(
186
+ 'npm install -g @geekbeer/minion@latest --registry http://verdaccio:4873',
187
+ 'net stop minion-agent',
188
+ 'net start minion-agent',
189
+ ),
113
190
  deferred: true,
114
191
  }
115
192
  commands['status-services'] = {
@@ -4,11 +4,12 @@
4
4
  * Same as linux/routes/chat.js but with Windows-compatible PATH and shell construction.
5
5
  *
6
6
  * Endpoints:
7
- * POST /api/chat - Send message, get SSE stream
8
- * GET /api/chat/session - Get active session (messages + session_id)
9
- * POST /api/chat/clear - Clear session and start fresh
10
- * POST /api/chat/abort - Kill the active LLM CLI process
11
- * POST /api/chat/reset - Summarize conversation and start fresh session
7
+ * POST /api/chat - Send message, get SSE stream
8
+ * GET /api/chat/session - Get active session (messages + session_id)
9
+ * POST /api/chat/clear - Clear session and start fresh
10
+ * POST /api/chat/abort - Kill the active LLM CLI process
11
+ * POST /api/chat/reset - Summarize conversation and start fresh session
12
+ * POST /api/chat/end-of-day - Generate daily log + extract memories from conversation
12
13
  */
13
14
 
14
15
  const { spawn } = require('child_process')
@@ -17,7 +18,10 @@ const path = require('path')
17
18
  const { verifyToken } = require('../../core/lib/auth')
18
19
  const { config } = require('../../core/config')
19
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')
20
23
  const { buildExtendedPath } = require('../../core/lib/platform')
24
+ const { runEndOfDay } = require('../../core/lib/end-of-day')
21
25
 
22
26
  let activeChatChild = null
23
27
 
@@ -34,7 +38,7 @@ async function chatRoutes(fastify) {
34
38
  return { success: false, error: 'message is required' }
35
39
  }
36
40
 
37
- const prompt = context ? buildContextPrefix(message, context) : message
41
+ const prompt = await buildContextPrefix(message, context, session_id)
38
42
  const currentSessionId = session_id || null
39
43
 
40
44
  if (currentSessionId) {
@@ -142,10 +146,45 @@ async function chatRoutes(fastify) {
142
146
  console.log(`[Chat] session reset with summary (${summary?.length || 0} chars)`)
143
147
  return { success: true, summary }
144
148
  })
149
+
150
+ // POST /api/chat/end-of-day - Generate daily log + extract memories
151
+ fastify.post('/api/chat/end-of-day', async (request, reply) => {
152
+ if (!verifyToken(request)) {
153
+ reply.code(401)
154
+ return { success: false, error: 'Unauthorized' }
155
+ }
156
+
157
+ const { clear_session = false } = request.body || {}
158
+
159
+ const result = await runEndOfDay({
160
+ runQuickLlmCall,
161
+ clearSession: clear_session,
162
+ })
163
+
164
+ return { success: true, ...result }
165
+ })
145
166
  }
146
167
 
147
- function buildContextPrefix(message, context) {
168
+ async function buildContextPrefix(message, context, sessionId) {
148
169
  const parts = []
170
+
171
+ // Inject memory + daily logs on new sessions only (not on --resume)
172
+ 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
+ }
186
+ }
187
+
149
188
  if (context) {
150
189
  switch (context.type) {
151
190
  case 'skill':
@@ -218,7 +257,8 @@ function streamLlmResponse(res, prompt, sessionId) {
218
257
 
219
258
  const args = ['-p', '--verbose', '--model', 'sonnet', '--output-format', 'stream-json']
220
259
  if (sessionId) args.push('--resume', sessionId)
221
- args.push(prompt)
260
+ // Prompt is passed via stdin (not as CLI argument) to avoid
261
+ // shell argument parsing issues with spaces/special characters.
222
262
 
223
263
  console.log(`[Chat] spawning: ${binary} ${sessionId ? `--resume ${sessionId}` : '(new session)'}`)
224
264
 
@@ -236,6 +276,8 @@ function streamLlmResponse(res, prompt, sessionId) {
236
276
  })
237
277
 
238
278
  activeChatChild = child
279
+ // Write prompt to stdin and close — claude -p reads from stdin when no positional arg
280
+ child.stdin.write(prompt)
239
281
  child.stdin.end()
240
282
 
241
283
  console.log(`[Chat] child PID: ${child.pid}`)
@@ -401,7 +443,7 @@ function runQuickLlmCall(prompt) {
401
443
  const binary = fs.existsSync(binaryPath) ? binaryPath : binaryName
402
444
  const extendedPath = buildExtendedPath(config.HOME_DIR)
403
445
 
404
- const args = ['-p', '--model', 'haiku', '--max-turns', '1', '--output-format', 'json', prompt]
446
+ const args = ['-p', '--model', 'haiku', '--max-turns', '1', '--output-format', 'json']
405
447
 
406
448
  const child = spawn(binary, args, {
407
449
  cwd: config.HOME_DIR,
@@ -416,6 +458,7 @@ function runQuickLlmCall(prompt) {
416
458
  },
417
459
  })
418
460
 
461
+ child.stdin.write(prompt)
419
462
  child.stdin.end()
420
463
 
421
464
  let stdout = ''
@@ -441,4 +484,4 @@ function runQuickLlmCall(prompt) {
441
484
  })
442
485
  }
443
486
 
444
- module.exports = { chatRoutes }
487
+ module.exports = { chatRoutes, runQuickLlmCall }
@@ -13,7 +13,12 @@ const { clearLlmCache } = require('../../core/lib/llm-checker')
13
13
  const { config, updateConfig } = require('../../core/config')
14
14
  const { resolveEnvFilePath } = require('../../core/lib/platform')
15
15
 
16
- const ALLOWED_ENV_KEYS = ['LLM_COMMAND']
16
+ const reflectionScheduler = require('../../core/lib/reflection-scheduler')
17
+
18
+ const ALLOWED_ENV_KEYS = ['LLM_COMMAND', 'REFLECTION_TIME', 'TIMEZONE']
19
+
20
+ /** Keys that trigger a reflection scheduler reschedule when changed */
21
+ const REFLECTION_KEYS = ['REFLECTION_TIME', 'TIMEZONE']
17
22
 
18
23
  const BACKUP_PATHS = [
19
24
  '~/.claude',
@@ -217,6 +222,12 @@ function configRoutes(fastify, _opts, done) {
217
222
  updateConfig(key, value)
218
223
 
219
224
  clearLlmCache()
225
+
226
+ // Reschedule reflection if relevant key changed
227
+ if (REFLECTION_KEYS.includes(key)) {
228
+ reflectionScheduler.reschedule()
229
+ }
230
+
220
231
  return { success: true, restart_required: false }
221
232
  } catch (err) {
222
233
  console.error(`[Config] Failed to update ${key} in ${envPath}:`, err.message)
@@ -17,6 +17,7 @@ const executionStore = require('../core/stores/execution-store')
17
17
  const routineStore = require('../core/stores/routine-store')
18
18
  const logManager = require('../core/lib/log-manager')
19
19
  const { MARKER_DIR, buildExtendedPath } = require('../core/lib/platform')
20
+ const variableStore = require('../core/stores/variable-store')
20
21
  const { activeSessions } = require('./workflow-runner')
21
22
 
22
23
  const activeJobs = new Map()
@@ -105,8 +106,11 @@ async function executeRoutineSession(routine, executionId, skillNames) {
105
106
  const escapedPrompt = prompt.replace(/'/g, "''")
106
107
  const llmCommand = config.LLM_COMMAND.replace(/\{prompt\}/g, escapedPrompt)
107
108
 
109
+ // Inject minion variables/secrets (routines don't receive HQ vars)
110
+ const injectedEnv = variableStore.buildEnv()
108
111
  const env = {
109
112
  ...process.env,
113
+ ...injectedEnv,
110
114
  HOME: homeDir,
111
115
  USERPROFILE: homeDir,
112
116
  PATH: extendedPath,
package/win/server.js CHANGED
@@ -16,6 +16,7 @@ const fastify = require('fastify')({ logger: true })
16
16
  const { config, validate, isHqConfigured } = require('../core/config')
17
17
  const { sendHeartbeat } = require('../core/api')
18
18
  const { version } = require('../package.json')
19
+ const { getCapabilities } = require('../core/lib/capability-checker')
19
20
  const workflowStore = require('../core/stores/workflow-store')
20
21
  const routineStore = require('../core/stores/routine-store')
21
22
 
@@ -30,12 +31,13 @@ const routineRunner = require('./routine-runner')
30
31
  // Pull-model daemons (from core/)
31
32
  const stepPoller = require('../core/lib/step-poller')
32
33
  const revisionWatcher = require('../core/lib/revision-watcher')
34
+ const reflectionScheduler = require('../core/lib/reflection-scheduler')
33
35
  const { commandRoutes, getProcessManager, getAllowedCommands } = require('./routes/commands')
34
36
  const { terminalRoutes, cleanupSessions } = require('./routes/terminal')
35
37
  const { startTerminalServer, stopTerminalServer } = require('./terminal-server')
36
38
  const { fileRoutes } = require('./routes/files')
37
39
  const { directiveRoutes } = require('./routes/directives')
38
- const { chatRoutes } = require('./routes/chat')
40
+ const { chatRoutes, runQuickLlmCall } = require('./routes/chat')
39
41
  const { configRoutes } = require('./routes/config')
40
42
 
41
43
  // Compatible route modules (reused from Linux)
@@ -45,6 +47,9 @@ const { skillRoutes } = require('../core/routes/skills')
45
47
  const { workflowRoutes } = require('../core/routes/workflows')
46
48
  const { routineRoutes } = require('../core/routes/routines')
47
49
  const { authRoutes } = require('../core/routes/auth')
50
+ const { variableRoutes } = require('../core/routes/variables')
51
+ const { memoryRoutes } = require('../core/routes/memory')
52
+ const { dailyLogRoutes } = require('../core/routes/daily-logs')
48
53
 
49
54
  // Validate configuration
50
55
  validate()
@@ -69,7 +74,7 @@ async function shutdown(signal) {
69
74
  if (isHqConfigured()) {
70
75
  try {
71
76
  await Promise.race([
72
- sendHeartbeat({ status: 'offline', version }),
77
+ sendHeartbeat({ status: 'offline', version, capabilities: getCapabilities() }),
73
78
  new Promise(resolve => setTimeout(resolve, 3000)),
74
79
  ])
75
80
  } catch {
@@ -79,6 +84,7 @@ async function shutdown(signal) {
79
84
 
80
85
  stepPoller.stop()
81
86
  revisionWatcher.stop()
87
+ reflectionScheduler.stop()
82
88
  workflowRunner.stopAll()
83
89
  routineRunner.stopAll()
84
90
  stopTerminalServer()
@@ -192,6 +198,9 @@ async function registerRoutes(app) {
192
198
  await app.register(workflowRoutes, { workflowRunner })
193
199
  await app.register(routineRoutes, { routineRunner })
194
200
  await app.register(authRoutes)
201
+ await app.register(variableRoutes)
202
+ await app.register(memoryRoutes)
203
+ await app.register(dailyLogRoutes)
195
204
 
196
205
  // Windows-specific routes
197
206
  await app.register(commandRoutes)
@@ -249,20 +258,23 @@ async function start() {
249
258
  console.error('[Server] Failed to load cached routines:', err.message)
250
259
  }
251
260
 
261
+ // Start reflection scheduler (self-reflection time)
262
+ reflectionScheduler.start(runQuickLlmCall)
263
+
252
264
  if (isHqConfigured()) {
253
265
  console.log(`[Server] HQ URL: ${config.HQ_URL}`)
254
266
 
255
267
  // Send initial online heartbeat
256
268
  const { getStatus } = require('../core/routes/health')
257
269
  const { currentTask } = getStatus()
258
- sendHeartbeat({ status: 'online', current_task: currentTask, version }).catch(err => {
270
+ sendHeartbeat({ status: 'online', current_task: currentTask, version, capabilities: getCapabilities() }).catch(err => {
259
271
  console.error('[Heartbeat] Initial heartbeat failed:', err.message)
260
272
  })
261
273
 
262
274
  // Start periodic heartbeat
263
275
  heartbeatTimer = setInterval(() => {
264
276
  const { currentStatus, currentTask } = getStatus()
265
- sendHeartbeat({ status: currentStatus, current_task: currentTask, version }).catch(err => {
277
+ sendHeartbeat({ status: currentStatus, current_task: currentTask, version, capabilities: getCapabilities() }).catch(err => {
266
278
  console.error('[Heartbeat] Periodic heartbeat failed:', err.message)
267
279
  })
268
280
  }, HEARTBEAT_INTERVAL_MS)
@@ -24,6 +24,7 @@ const executionStore = require('../core/stores/execution-store')
24
24
  const workflowStore = require('../core/stores/workflow-store')
25
25
  const logManager = require('../core/lib/log-manager')
26
26
  const { buildExtendedPath } = require('../core/lib/platform')
27
+ const variableStore = require('../core/stores/variable-store')
27
28
 
28
29
  // Active cron jobs keyed by workflow ID
29
30
  const activeJobs = new Map()
@@ -112,9 +113,11 @@ async function executeWorkflowSession(workflow, executionId, skillNames, options
112
113
  const escapedPrompt = prompt.replace(/'/g, "''")
113
114
  const llmCommand = config.LLM_COMMAND.replace(/\{prompt\}/g, escapedPrompt)
114
115
 
115
- // Build environment
116
+ // Build environment: base + minion variables/secrets + extra vars from HQ
117
+ const injectedEnv = variableStore.buildEnv(options.extraEnv || {})
116
118
  const env = {
117
119
  ...process.env,
120
+ ...injectedEnv,
118
121
  HOME: homeDir,
119
122
  USERPROFILE: homeDir,
120
123
  PATH: extendedPath,