@geekbeer/minion 2.49.1 → 2.51.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.
package/core/config.js CHANGED
@@ -13,7 +13,6 @@
13
13
  * - AGENT_PORT: Port for the local agent server (default: 8080)
14
14
  * - MINION_USER: System user running the agent (used to resolve home directory)
15
15
  * - REFLECTION_TIME: Daily self-reflection time in HH:MM format (e.g., "03:00")
16
- * - TIMEZONE: Timezone for reflection schedule (default: "Asia/Tokyo")
17
16
  */
18
17
 
19
18
  const os = require('os')
@@ -92,8 +91,8 @@ const config = {
92
91
 
93
92
  // Self-reflection schedule (自己反省時間)
94
93
  // Daily time to automatically run end-of-day processing (log + memory extraction + session clear)
94
+ // Time is interpreted in the system's local timezone (set via TZ env var or OS settings)
95
95
  REFLECTION_TIME: process.env.REFLECTION_TIME ?? '03:00', // "HH:MM" format, empty = disabled
96
- TIMEZONE: process.env.TIMEZONE || 'Asia/Tokyo',
97
96
  }
98
97
 
99
98
  /**
@@ -2,14 +2,16 @@
2
2
  * Reflection Scheduler (自己反省スケジューラ)
3
3
  *
4
4
  * Built-in server-level scheduler that triggers end-of-day processing
5
- * (daily log generation + memory extraction + session clear) at a configured time.
5
+ * (daily log generation + memory extraction) at a configured time.
6
+ * Chat session is preserved to maintain conversation continuity.
6
7
  *
7
8
  * Unlike routines (user-deletable), this is a core architectural feature
8
9
  * that ensures the minion regularly consolidates its conversation history.
9
10
  *
10
11
  * Configuration:
11
12
  * REFLECTION_TIME - Time to run daily reflection (format: "HH:MM", e.g., "03:00")
12
- * TIMEZONE - Timezone for the schedule (default: "Asia/Tokyo")
13
+ *
14
+ * Time is interpreted in the system's local timezone (set via TZ env var or OS settings).
13
15
  *
14
16
  * @module core/lib/reflection-scheduler
15
17
  */
@@ -42,6 +44,18 @@ function parseToCron(timeStr) {
42
44
  return `0 ${minute} ${hour} * * *`
43
45
  }
44
46
 
47
+ /**
48
+ * Get the system's local timezone name.
49
+ * @returns {string} IANA timezone string (e.g., "Asia/Tokyo", "UTC")
50
+ */
51
+ function getSystemTimezone() {
52
+ try {
53
+ return Intl.DateTimeFormat().resolvedOptions().timeZone
54
+ } catch {
55
+ return 'UTC'
56
+ }
57
+ }
58
+
45
59
  /**
46
60
  * Start the reflection scheduler.
47
61
  * If REFLECTION_TIME is not configured, the scheduler remains inactive.
@@ -63,15 +77,15 @@ function start(runQuickLlmCall) {
63
77
  return
64
78
  }
65
79
 
66
- const timezone = config.TIMEZONE || 'Asia/Tokyo'
80
+ const systemTz = getSystemTimezone()
67
81
 
68
82
  try {
69
- cronJob = new Cron(cronExpr, { timezone }, async () => {
83
+ cronJob = new Cron(cronExpr, async () => {
70
84
  await runReflection()
71
85
  })
72
86
 
73
87
  const nextRun = cronJob.nextRun()
74
- console.log(`[ReflectionScheduler] Scheduled at ${reflectionTime} (${timezone}), next run: ${nextRun ? nextRun.toISOString() : 'unknown'}`)
88
+ console.log(`[ReflectionScheduler] Scheduled at ${reflectionTime} (system timezone: ${systemTz}), next run: ${nextRun ? nextRun.toISOString() : 'unknown'}`)
75
89
  } catch (err) {
76
90
  console.error(`[ReflectionScheduler] Failed to start: ${err.message}`)
77
91
  }
@@ -89,7 +103,7 @@ function stop() {
89
103
  }
90
104
 
91
105
  /**
92
- * Reschedule after config change (e.g., REFLECTION_TIME or TIMEZONE updated via API).
106
+ * Reschedule after config change (e.g., REFLECTION_TIME updated via API).
93
107
  * Requires that start() was called previously with a runQuickLlmCall function.
94
108
  */
95
109
  function reschedule() {
@@ -122,7 +136,7 @@ async function runReflection() {
122
136
  try {
123
137
  const result = await runEndOfDay({
124
138
  runQuickLlmCall: llmCallFn,
125
- clearSession: true,
139
+ clearSession: false,
126
140
  })
127
141
 
128
142
  if (result.daily_log) {
@@ -143,13 +157,12 @@ async function runReflection() {
143
157
  */
144
158
  function getStatus() {
145
159
  const reflectionTime = config.REFLECTION_TIME || ''
146
- const timezone = config.TIMEZONE || 'Asia/Tokyo'
147
160
  const nextRun = cronJob ? cronJob.nextRun() : null
148
161
 
149
162
  return {
150
163
  enabled: !!cronJob,
151
164
  reflection_time: reflectionTime,
152
- timezone,
165
+ timezone: getSystemTimezone(),
153
166
  next_run: nextRun ? nextRun.toISOString() : null,
154
167
  }
155
168
  }
@@ -51,11 +51,15 @@ async function healthRoutes(fastify) {
51
51
 
52
52
  // Get current status
53
53
  fastify.get('/api/status', async () => {
54
+ let systemTimezone = 'UTC'
55
+ try { systemTimezone = Intl.DateTimeFormat().resolvedOptions().timeZone } catch {}
56
+
54
57
  return {
55
58
  status: currentStatus,
56
59
  current_task: currentTask,
57
60
  uptime: process.uptime(),
58
61
  version,
62
+ timezone: systemTimezone,
59
63
  timestamp: new Date().toISOString(),
60
64
  llm_services: getLlmServices(),
61
65
  llm_command_configured: isLlmCommandConfigured(),
@@ -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
  })
@@ -42,8 +42,8 @@ function isValidDate(date) {
42
42
  }
43
43
 
44
44
  /**
45
- * List all daily logs with date and file size.
46
- * @returns {Promise<Array<{ date: string, size: number }>>} Sorted descending by date
45
+ * List all daily logs with date, file size, and timestamps.
46
+ * @returns {Promise<Array<{ date: string, size: number, created_at: string, updated_at: string }>>} Sorted descending by date
47
47
  */
48
48
  async function listLogs() {
49
49
  await ensureDir()
@@ -61,7 +61,12 @@ async function listLogs() {
61
61
  if (!isValidDate(date)) continue
62
62
  try {
63
63
  const stat = await fs.stat(path.join(LOGS_DIR, file))
64
- logs.push({ date, size: stat.size })
64
+ logs.push({
65
+ date,
66
+ size: stat.size,
67
+ created_at: stat.birthtime.toISOString(),
68
+ updated_at: stat.mtime.toISOString(),
69
+ })
65
70
  } catch {
66
71
  // Skip unreadable
67
72
  }
@@ -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: '',
@@ -179,10 +179,11 @@ Configuration via `PUT /api/config/env`:
179
179
 
180
180
  | Key | Format | Default | Description |
181
181
  |-----|--------|---------|-------------|
182
- | `REFLECTION_TIME` | `HH:MM` | (disabled) | Daily reflection time (e.g., `"03:00"`) |
183
- | `TIMEZONE` | IANA tz | `Asia/Tokyo` | Timezone for the schedule |
182
+ | `REFLECTION_TIME` | `HH:MM` | `03:00` | Daily reflection time (interpreted in system timezone) |
184
183
 
185
- Example set reflection at 3:00 AM JST:
184
+ Timezone follows the system setting (Docker: `TZ` env var, VPS: `timedatectl`).
185
+
186
+ Example — set reflection at 3:00 AM:
186
187
  ```bash
187
188
  curl -X PUT /api/config/env \
188
189
  -H "Authorization: Bearer $TOKEN" \
@@ -201,7 +202,7 @@ Changes via the config API take effect immediately (no restart required).
201
202
  | GET | `/api/config/backup` | Download config files as tar.gz |
202
203
  | POST | `/api/config/restore` | Restore config from tar.gz |
203
204
 
204
- Allowed keys: `LLM_COMMAND`, `REFLECTION_TIME`, `TIMEZONE`
205
+ Allowed keys: `LLM_COMMAND`, `REFLECTION_TIME`
205
206
 
206
207
  ### Commands
207
208
 
@@ -252,6 +253,64 @@ Response:
252
253
 
253
254
  PUT body: `{ "content": "markdown string" }`
254
255
 
256
+ ### Project Variables (PM only)
257
+
258
+ | Method | Endpoint | Description |
259
+ |--------|----------|-------------|
260
+ | POST | `/api/minion/me/project/[id]/variables/[key]` | プロジェクト変数を設定(upsert) |
261
+ | DELETE | `/api/minion/me/project/[id]/variables/[key]` | プロジェクト変数を削除 |
262
+
263
+ POST body:
264
+ ```json
265
+ {
266
+ "value": "variable value (max 2000 chars)"
267
+ }
268
+ ```
269
+
270
+ Key format: `/^[A-Za-z_][A-Za-z0-9_]{0,99}$/`(環境変数命名規約)
271
+
272
+ Response:
273
+ ```json
274
+ {
275
+ "success": true,
276
+ "key": "MY_VAR",
277
+ "value": "variable value"
278
+ }
279
+ ```
280
+
281
+ プロジェクト変数はワークフロー実行時に `extra_env` としてミニオンに渡される。
282
+
283
+ ### Workflow Variables (PM only)
284
+
285
+ | Method | Endpoint | Description |
286
+ |--------|----------|-------------|
287
+ | POST | `/api/minion/me/project/[id]/workflows/[wfId]/variables/[key]` | ワークフロー変数を設定(upsert) |
288
+ | DELETE | `/api/minion/me/project/[id]/workflows/[wfId]/variables/[key]` | ワークフロー変数を削除 |
289
+
290
+ POST body:
291
+ ```json
292
+ {
293
+ "value": "variable value (max 2000 chars)"
294
+ }
295
+ ```
296
+
297
+ Key format: `/^[A-Za-z_][A-Za-z0-9_]{0,99}$/`
298
+
299
+ Response:
300
+ ```json
301
+ {
302
+ "success": true,
303
+ "key": "MY_VAR",
304
+ "value": "variable value"
305
+ }
306
+ ```
307
+
308
+ ワークフロー変数はプロジェクト変数を上書きする(同名キーの場合)。実行時のマージ順序:
309
+ 1. ミニオンローカル変数
310
+ 2. ミニオンシークレット
311
+ 3. プロジェクト変数
312
+ 4. ワークフロー変数(最優先)
313
+
255
314
  ### Workflows (project-scoped, versioned)
256
315
 
257
316
  | Method | Endpoint | Description |
@@ -20,13 +20,28 @@ SKILL.md フォーマット:
20
20
  ```markdown
21
21
  ---
22
22
  name: my-skill
23
- display_name: My Skill
24
23
  description: What this skill does
24
+ requires:
25
+ mcp_servers: [playwright, supabase]
26
+ cli_tools: [git, node]
25
27
  ---
26
28
 
27
29
  Skill instructions here...
28
30
  ```
29
31
 
32
+ フロントマターのフィールド:
33
+
34
+ | Field | Required | Description |
35
+ |-------|----------|-------------|
36
+ | `name` | Yes | スキル識別子(小文字・ハイフン・数字) |
37
+ | `description` | Yes | スキルの説明 |
38
+ | `requires` | No | 実行に必要な依存関係 |
39
+ | `requires.mcp_servers` | No | 必要な MCP サーバー名のリスト |
40
+ | `requires.cli_tools` | No | 必要な CLI ツール名のリスト |
41
+
42
+ `requires` を宣言すると、ワークフロー実行前にミニオンの環境と照合する事前チェック(readiness check)が行われる。
43
+ 未宣言の場合は常に実行可能と見なされる。
44
+
30
45
  ### 2. HQ に反映する
31
46
 
32
47
  ```bash
@@ -308,19 +308,6 @@ do_setup() {
308
308
  ENV_CONTENT+="MINION_USER=${TARGET_USER}\n"
309
309
  ENV_CONTENT+="REFLECTION_TIME=03:00\n"
310
310
 
311
- # Detect system timezone (IANA format)
312
- local SYS_TZ=""
313
- if command -v timedatectl &>/dev/null; then
314
- SYS_TZ=$(timedatectl show --property=Timezone --value 2>/dev/null || true)
315
- fi
316
- if [ -z "$SYS_TZ" ] && [ -f /etc/timezone ]; then
317
- SYS_TZ=$(cat /etc/timezone 2>/dev/null | tr -d '[:space:]')
318
- fi
319
- if [ -z "$SYS_TZ" ] && [ -L /etc/localtime ]; then
320
- SYS_TZ=$(readlink /etc/localtime | sed 's|.*/zoneinfo/||')
321
- fi
322
- ENV_CONTENT+="TIMEZONE=${SYS_TZ:-Asia/Tokyo}\n"
323
-
324
311
  echo -e "$ENV_CONTENT" | $SUDO tee /opt/minion-agent/.env > /dev/null
325
312
  $SUDO chown "${TARGET_USER}:${TARGET_USER}" /opt/minion-agent/.env
326
313
  echo " -> /opt/minion-agent/.env generated"
@@ -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) {
@@ -18,10 +18,10 @@ const { resolveEnvFilePath: resolveEnvFilePathFromPlatform } = require('../../co
18
18
  const reflectionScheduler = require('../../core/lib/reflection-scheduler')
19
19
 
20
20
  /** Keys that can be read/written via the config API */
21
- const ALLOWED_ENV_KEYS = ['LLM_COMMAND', 'REFLECTION_TIME', 'TIMEZONE']
21
+ const ALLOWED_ENV_KEYS = ['LLM_COMMAND', 'REFLECTION_TIME']
22
22
 
23
23
  /** Keys that trigger a reflection scheduler reschedule when changed */
24
- const REFLECTION_KEYS = ['REFLECTION_TIME', 'TIMEZONE']
24
+ const REFLECTION_KEYS = ['REFLECTION_TIME']
25
25
 
26
26
  const BACKUP_PATHS = [
27
27
  '~/.claude',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@geekbeer/minion",
3
- "version": "2.49.1",
3
+ "version": "2.51.1",
4
4
  "description": "AI Agent runtime for Minion - manages status and skill deployment on VPS",
5
5
  "main": "linux/server.js",
6
6
  "bin": {
@@ -52,7 +52,7 @@ function detectProcessManager() {
52
52
  * @param {string} npmInstallCmd - The npm install command to run
53
53
  * @param {string} stopCmd - Command/script block to stop the agent
54
54
  * @param {string} startCmd - Command/script block to start the agent
55
- * @returns {string} - Shell command that writes and launches the update script
55
+ * @returns {string} - Path to the generated update script (.ps1)
56
56
  */
57
57
  function buildUpdateScript(npmInstallCmd, stopCmd, startCmd) {
58
58
  const dataDir = path.join(os.homedir(), '.minion')
@@ -83,11 +83,13 @@ function buildUpdateScript(npmInstallCmd, stopCmd, startCmd) {
83
83
  `}`,
84
84
  ].join('\n')
85
85
 
86
- // Write the script to disk and launch it detached
86
+ // Write the script to disk
87
87
  try { fs.mkdirSync(dataDir, { recursive: true }) } catch { /* exists */ }
88
88
  fs.writeFileSync(scriptPath, ps1, 'utf-8')
89
89
 
90
- return `powershell -ExecutionPolicy Bypass -Command "Start-Process powershell -ArgumentList '-ExecutionPolicy Bypass -WindowStyle Hidden -File \\"${scriptPath}\\"' -WindowStyle Hidden"`
90
+ // Return script path caller uses spawn() with detached:true to launch it,
91
+ // avoiding cmd.exe quoting issues and -WindowStyle Hidden hangs in non-interactive sessions.
92
+ return scriptPath
91
93
  }
92
94
 
93
95
  /**
@@ -113,20 +115,20 @@ function buildAllowedCommands(procMgr) {
113
115
  }
114
116
  commands['update-agent'] = {
115
117
  description: 'Update @geekbeer/minion to latest version and restart',
116
- command: buildUpdateScript(
118
+ spawnArgs: ['powershell', ['-NoProfile', '-ExecutionPolicy', 'Bypass', '-File', buildUpdateScript(
117
119
  'npm install -g @geekbeer/minion@latest',
118
120
  stopBlock,
119
121
  startBlock,
120
- ),
122
+ )]],
121
123
  deferred: true,
122
124
  }
123
125
  commands['update-agent-dev'] = {
124
126
  description: 'Update @geekbeer/minion from Verdaccio (dev) and restart',
125
- command: buildUpdateScript(
127
+ spawnArgs: ['powershell', ['-NoProfile', '-ExecutionPolicy', 'Bypass', '-File', buildUpdateScript(
126
128
  'npm install -g @geekbeer/minion@latest --registry http://verdaccio:4873',
127
129
  stopBlock,
128
130
  startBlock,
129
- ),
131
+ )]],
130
132
  deferred: true,
131
133
  }
132
134
  commands['status-services'] = {
@@ -141,20 +143,20 @@ function buildAllowedCommands(procMgr) {
141
143
  }
142
144
  commands['update-agent'] = {
143
145
  description: 'Update @geekbeer/minion to latest version and restart',
144
- command: buildUpdateScript(
146
+ spawnArgs: ['powershell', ['-NoProfile', '-ExecutionPolicy', 'Bypass', '-File', buildUpdateScript(
145
147
  'npm install -g @geekbeer/minion@latest',
146
148
  'nssm stop minion-agent',
147
149
  'nssm start minion-agent',
148
- ),
150
+ )]],
149
151
  deferred: true,
150
152
  }
151
153
  commands['update-agent-dev'] = {
152
154
  description: 'Update @geekbeer/minion from Verdaccio (dev) and restart',
153
- command: buildUpdateScript(
155
+ spawnArgs: ['powershell', ['-NoProfile', '-ExecutionPolicy', 'Bypass', '-File', buildUpdateScript(
154
156
  'npm install -g @geekbeer/minion@latest --registry http://verdaccio:4873',
155
157
  'nssm stop minion-agent',
156
158
  'nssm start minion-agent',
157
- ),
159
+ )]],
158
160
  deferred: true,
159
161
  }
160
162
  commands['restart-display'] = {
@@ -173,20 +175,20 @@ function buildAllowedCommands(procMgr) {
173
175
  }
174
176
  commands['update-agent'] = {
175
177
  description: 'Update @geekbeer/minion to latest version and restart',
176
- command: buildUpdateScript(
178
+ spawnArgs: ['powershell', ['-NoProfile', '-ExecutionPolicy', 'Bypass', '-File', buildUpdateScript(
177
179
  'npm install -g @geekbeer/minion@latest',
178
180
  'net stop minion-agent',
179
181
  'net start minion-agent',
180
- ),
182
+ )]],
181
183
  deferred: true,
182
184
  }
183
185
  commands['update-agent-dev'] = {
184
186
  description: 'Update @geekbeer/minion from Verdaccio (dev) and restart',
185
- command: buildUpdateScript(
187
+ spawnArgs: ['powershell', ['-NoProfile', '-ExecutionPolicy', 'Bypass', '-File', buildUpdateScript(
186
188
  'npm install -g @geekbeer/minion@latest --registry http://verdaccio:4873',
187
189
  'net stop minion-agent',
188
190
  'net start minion-agent',
189
- ),
191
+ )]],
190
192
  deferred: true,
191
193
  }
192
194
  commands['status-services'] = {
@@ -390,17 +390,10 @@ function Invoke-Setup {
390
390
  Write-Step 4 $totalSteps "Creating config directory and .env..."
391
391
  New-Item -Path $DataDir -ItemType Directory -Force | Out-Null
392
392
  New-Item -Path $LogDir -ItemType Directory -Force | Out-Null
393
- # Detect system timezone (IANA format)
394
- $sysTz = 'Asia/Tokyo'
395
- try {
396
- $tzInfo = Get-TimeZone
397
- if ($tzInfo.Id) { $sysTz = $tzInfo.Id }
398
- } catch {}
399
393
  $envValues = @{
400
394
  'AGENT_PORT' = '8080'
401
395
  'MINION_USER' = $env:USERNAME
402
396
  'REFLECTION_TIME' = '03:00'
403
- 'TIMEZONE' = $sysTz
404
397
  }
405
398
  if ($HqUrl) { $envValues['HQ_URL'] = $HqUrl }
406
399
  if ($ApiToken) { $envValues['API_TOKEN'] = $ApiToken }
@@ -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) {
@@ -4,7 +4,7 @@
4
4
  * Same API as routes/commands.js but uses win/process-manager.js
5
5
  */
6
6
 
7
- const { exec } = require('child_process')
7
+ const { exec, spawn } = require('child_process')
8
8
  const { promisify } = require('util')
9
9
  const execAsync = promisify(exec)
10
10
 
@@ -55,10 +55,28 @@ async function commandRoutes(fastify) {
55
55
  if (allowedCommand.deferred) {
56
56
  console.log(`[Command] Scheduling deferred command: ${command}`)
57
57
  setTimeout(() => {
58
- exec(allowedCommand.command, { timeout: 60000, shell: true }, (err) => {
59
- if (err) console.error(`[Command] Deferred command failed: ${command} - ${err.message}`)
60
- else console.log(`[Command] Deferred command completed: ${command}`)
61
- })
58
+ if (allowedCommand.spawnArgs) {
59
+ // Use spawn with detached:true for update scripts.
60
+ // Avoids cmd.exe quoting issues and -WindowStyle Hidden hangs
61
+ // in non-interactive sessions that caused HQ update timeouts.
62
+ const [cmd, args] = allowedCommand.spawnArgs
63
+ try {
64
+ const child = spawn(cmd, args, {
65
+ detached: true,
66
+ stdio: 'ignore',
67
+ windowsHide: true,
68
+ })
69
+ child.unref()
70
+ console.log(`[Command] Deferred command spawned: ${command} (pid: ${child.pid})`)
71
+ } catch (err) {
72
+ console.error(`[Command] Deferred spawn failed: ${command} - ${err.message}`)
73
+ }
74
+ } else {
75
+ exec(allowedCommand.command, { timeout: 60000, shell: true }, (err) => {
76
+ if (err) console.error(`[Command] Deferred command failed: ${command} - ${err.message}`)
77
+ else console.log(`[Command] Deferred command completed: ${command}`)
78
+ })
79
+ }
62
80
  }, 1000)
63
81
 
64
82
  return {
@@ -15,10 +15,10 @@ const { resolveEnvFilePath } = require('../../core/lib/platform')
15
15
 
16
16
  const reflectionScheduler = require('../../core/lib/reflection-scheduler')
17
17
 
18
- const ALLOWED_ENV_KEYS = ['LLM_COMMAND', 'REFLECTION_TIME', 'TIMEZONE']
18
+ const ALLOWED_ENV_KEYS = ['LLM_COMMAND', 'REFLECTION_TIME']
19
19
 
20
20
  /** Keys that trigger a reflection scheduler reschedule when changed */
21
- const REFLECTION_KEYS = ['REFLECTION_TIME', 'TIMEZONE']
21
+ const REFLECTION_KEYS = ['REFLECTION_TIME']
22
22
 
23
23
  const BACKUP_PATHS = [
24
24
  '~/.claude',