@geekbeer/minion 3.42.2 → 3.42.3

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@geekbeer/minion",
3
- "version": "3.42.2",
3
+ "version": "3.42.3",
4
4
  "description": "AI Agent runtime for Minion - manages status and skill deployment on VPS",
5
5
  "main": "linux/server.js",
6
6
  "bin": {
@@ -80,16 +80,29 @@ function buildGracefulStopBlock(agentPort, apiToken) {
80
80
  }
81
81
 
82
82
  /**
83
- * Generate a temporary PowerShell script for updating the agent:
84
- * 1. Graceful shutdown via HTTP API (offline heartbeat)
85
- * 2. Stop service via NSSM
86
- * 3. Run npm install -g
87
- * 4. Start service via NSSM
88
- * 5. Remove the temporary updater service (self-cleanup)
83
+ * Generate a temporary PowerShell script for updating the agent.
84
+ *
85
+ * Flow:
86
+ * 1. End MinionWSL scheduled task (holds Fastify/ajv file handles via
87
+ * wsl-session-server.js in user session — previously caused EBUSY /
88
+ * partial installs that left node_modules/ajv/dist/refs/data.json
89
+ * missing and bricked the next startup).
90
+ * 2. Graceful shutdown via HTTP API (offline heartbeat).
91
+ * 3. Stop minion-agent via NSSM and WAIT until service is actually
92
+ * Stopped (poll sc.exe, not a fixed Start-Sleep).
93
+ * 4. Kill stray node.exe processes that still reference the minion
94
+ * package dir (workflow/routine runners, claude-code children).
95
+ * 5. Run npm install -g, then verify integrity by actually requiring
96
+ * fastify + the package.json from the installed location. If the
97
+ * install silently produced a broken tree, we detect it here
98
+ * (instead of bouncing an unstartable service).
99
+ * 6. On persistent integrity failure, wipe the package dir to force a
100
+ * clean reinstall on the next attempt.
101
+ * 7. Start minion-agent. Re-run the MinionWSL task if it existed.
102
+ * 8. Remove the temporary updater service (self-cleanup in finally).
89
103
  *
90
- * This script is registered as a temporary NSSM service ("minion-update")
91
- * so it runs independently of the minion-agent service and survives
92
- * the agent's stop/restart cycle.
104
+ * The script is registered as a temporary NSSM service ("minion-update")
105
+ * so it runs independently of the minion-agent service.
93
106
  *
94
107
  * @param {string} npmInstallCmd - The npm install command to run
95
108
  * @param {string} nssmPath - Absolute path to nssm.exe
@@ -111,6 +124,12 @@ function buildUpdateScript(npmInstallCmd, nssmPath, scriptName = 'update-agent',
111
124
  ? `${npmInstallCmd} --prefix "${npmPrefix}"`
112
125
  : npmInstallCmd
113
126
 
127
+ // Absolute path to the installed package dir (used for integrity check
128
+ // and, if integrity keeps failing, for a forced clean reinstall).
129
+ const pkgDir = npmPrefix
130
+ ? path.join(npmPrefix, 'node_modules', '@geekbeer', 'minion')
131
+ : ''
132
+
114
133
  const gracefulStop = buildGracefulStopBlock(agentPort, apiToken)
115
134
 
116
135
  const ps1 = [
@@ -121,38 +140,153 @@ function buildUpdateScript(npmInstallCmd, nssmPath, scriptName = 'update-agent',
121
140
  `# Use nssm.exe from data dir (copied there to avoid EBUSY on package files)`,
122
141
  `$nssm = '${path.join(dataDir, 'nssm.exe')}'`,
123
142
  `$logFile = '${logPath}'`,
143
+ `$pkgDir = '${pkgDir}'`,
124
144
  `function Log($msg) { "$(Get-Date -Format o) $msg" | Out-File -Append $logFile }`,
145
+ ``,
146
+ `# Poll until the service reaches Stopped (or timeout).`,
147
+ `function Wait-ServiceStopped($name, $timeoutSec) {`,
148
+ ` $deadline = (Get-Date).AddSeconds($timeoutSec)`,
149
+ ` while ((Get-Date) -lt $deadline) {`,
150
+ ` $svc = Get-Service -Name $name -ErrorAction SilentlyContinue`,
151
+ ` if (-not $svc -or $svc.Status -eq 'Stopped') { return $true }`,
152
+ ` Start-Sleep -Milliseconds 500`,
153
+ ` }`,
154
+ ` return $false`,
155
+ `}`,
156
+ ``,
157
+ `# Kill any node.exe whose command line references the minion package`,
158
+ `# (stale workflow/routine runners, wsl-session-entry children, etc.).`,
159
+ `# These hold file handles on node_modules and break npm install on Windows.`,
160
+ `function Kill-PackageProcesses {`,
161
+ ` try {`,
162
+ ` Get-CimInstance Win32_Process -Filter "Name='node.exe'" -ErrorAction SilentlyContinue |`,
163
+ ` Where-Object { $_.CommandLine -and ($_.CommandLine -match '@geekbeer.minion' -or $_.CommandLine -match 'wsl-session-entry') } |`,
164
+ ` ForEach-Object {`,
165
+ ` Log "Killing stray node.exe PID $($_.ProcessId): $($_.CommandLine)"`,
166
+ ` Stop-Process -Id $_.ProcessId -Force -ErrorAction SilentlyContinue`,
167
+ ` }`,
168
+ ` } catch { Log "Kill-PackageProcesses error: $_" }`,
169
+ `}`,
170
+ ``,
171
+ `# Load fastify + @geekbeer/minion/package.json from the installed tree.`,
172
+ `# Catches the "npm reports success but files are missing" failure mode`,
173
+ `# (the ajv/data.json crash we have been chasing).`,
174
+ `function Test-PackageIntegrity {`,
175
+ ` if (-not $pkgDir -or -not (Test-Path $pkgDir)) {`,
176
+ ` Log "Integrity check: pkgDir missing ($pkgDir)"`,
177
+ ` return $false`,
178
+ ` }`,
179
+ ` try {`,
180
+ ` Push-Location $pkgDir`,
181
+ ` $out = & node -e "require('fastify');require('./package.json');console.log('OK')" 2>&1`,
182
+ ` $code = $LASTEXITCODE`,
183
+ ` Pop-Location`,
184
+ ` if ($code -eq 0 -and "$out" -match 'OK') {`,
185
+ ` Log "Integrity check passed"`,
186
+ ` return $true`,
187
+ ` }`,
188
+ ` Log "Integrity check failed (exit $code): $out"`,
189
+ ` return $false`,
190
+ ` } catch {`,
191
+ ` try { Pop-Location } catch {}`,
192
+ ` Log "Integrity check error: $_"`,
193
+ ` return $false`,
194
+ ` }`,
195
+ `}`,
196
+ ``,
125
197
  `Log 'Update started'`,
198
+ `$wslTaskExists = $false`,
126
199
  `try {`,
200
+ ` # 1. Stop MinionWSL scheduled task so wsl-session-server.js releases`,
201
+ ` # Fastify/ajv file handles before npm install.`,
202
+ ` try {`,
203
+ ` $null = Get-ScheduledTask -TaskName 'MinionWSL' -ErrorAction Stop`,
204
+ ` $wslTaskExists = $true`,
205
+ ` Log 'Ending MinionWSL scheduled task...'`,
206
+ ` schtasks /End /TN "MinionWSL" 2>&1 | ForEach-Object { Log "schtasks: $_" }`,
207
+ ` } catch {`,
208
+ ` Log 'MinionWSL task not registered (skipping)'`,
209
+ ` }`,
210
+ ``,
211
+ ` # 2. Ask the agent to flush an offline heartbeat before we yank it.`,
127
212
  ` Log 'Requesting graceful shutdown...'`,
128
213
  gracefulStop,
129
- ` Log 'Stopping service via NSSM...'`,
130
- ` & $nssm stop minion-agent`,
131
- ` Start-Sleep -Seconds 3`,
132
- ` # Retry npm install up to 5 times (Windows may hold file locks briefly after service stop)`,
214
+ ``,
215
+ ` # 3. Stop the agent service and WAIT for it to actually be Stopped.`,
216
+ ` Log 'Stopping minion-agent service via NSSM...'`,
217
+ ` & $nssm stop minion-agent 2>&1 | ForEach-Object { Log "nssm: $_" }`,
218
+ ` if (-not (Wait-ServiceStopped 'minion-agent' 30)) {`,
219
+ ` Log 'WARNING: minion-agent did not reach Stopped within 30s'`,
220
+ ` } else {`,
221
+ ` Log 'minion-agent stopped'`,
222
+ ` }`,
223
+ ` # Give Windows a moment to release kernel file handles after exit.`,
224
+ ` Start-Sleep -Seconds 2`,
225
+ ``,
226
+ ` # 4. Kill any straggler node.exe that still hold package files.`,
227
+ ` Kill-PackageProcesses`,
228
+ ` Start-Sleep -Seconds 1`,
229
+ ``,
230
+ ` # 5. Retry npm install + integrity check up to 5 times.`,
133
231
  ` Log 'Installing package...'`,
134
232
  ` $installed = $false`,
135
233
  ` for ($attempt = 1; $attempt -le 5; $attempt++) {`,
234
+ ` Log "npm install attempt $attempt..."`,
136
235
  ` $out = & cmd /c "${fullNpmCmd} 2>&1"`,
137
- ` if ($LASTEXITCODE -eq 0) {`,
138
- ` Log "npm output: $out"`,
139
- ` $installed = $true`,
140
- ` break`,
236
+ ` $npmExit = $LASTEXITCODE`,
237
+ ` if ($npmExit -ne 0) {`,
238
+ ` Log "npm install attempt $attempt failed (exit $npmExit): $out"`,
239
+ ` } else {`,
240
+ ` Log "npm install attempt $attempt exit 0"`,
241
+ ` if (Test-PackageIntegrity) {`,
242
+ ` $installed = $true`,
243
+ ` break`,
244
+ ` }`,
141
245
  ` }`,
142
- ` Log "npm install attempt $attempt failed (exit $LASTEXITCODE): $out"`,
143
246
  ` if ($attempt -lt 5) {`,
247
+ ` # Before retrying, re-kill stragglers and (on later attempts) wipe`,
248
+ ` # the package dir so npm is forced to lay it down from scratch.`,
249
+ ` Kill-PackageProcesses`,
250
+ ` if ($attempt -ge 2 -and $pkgDir -and (Test-Path $pkgDir)) {`,
251
+ ` Log "Wiping package dir for clean reinstall: $pkgDir"`,
252
+ ` try { Remove-Item -Recurse -Force $pkgDir -ErrorAction Stop }`,
253
+ ` catch { Log "Remove-Item failed: $_" }`,
254
+ ` }`,
255
+ ` if ($attempt -ge 3) {`,
256
+ ` Log 'Running npm cache verify...'`,
257
+ ` & cmd /c 'npm cache verify 2>&1' | ForEach-Object { Log "npm cache: $_" }`,
258
+ ` }`,
144
259
  ` Log "Retrying in 10 seconds..."`,
145
260
  ` Start-Sleep -Seconds 10`,
146
261
  ` }`,
147
262
  ` }`,
148
- ` if (-not $installed) { throw "npm install failed after 5 attempts" }`,
149
- ` Log 'Starting service...'`,
150
- ` & $nssm start minion-agent`,
263
+ ` if (-not $installed) { throw "npm install / integrity check failed after 5 attempts" }`,
264
+ ``,
265
+ ` # 6. Start the agent service.`,
266
+ ` Log 'Starting minion-agent service...'`,
267
+ ` & $nssm start minion-agent 2>&1 | ForEach-Object { Log "nssm: $_" }`,
268
+ ``,
269
+ ` # 7. Re-run MinionWSL if it was registered.`,
270
+ ` if ($wslTaskExists) {`,
271
+ ` Log 'Running MinionWSL scheduled task...'`,
272
+ ` schtasks /Run /TN "MinionWSL" 2>&1 | ForEach-Object { Log "schtasks: $_" }`,
273
+ ` }`,
274
+ ``,
151
275
  ` Log 'Update completed successfully'`,
152
276
  `} catch {`,
153
277
  ` Log "Update failed: $_"`,
154
- ` Log 'Attempting to start service anyway...'`,
155
- ` & $nssm start minion-agent`,
278
+ ` # Do NOT blindly start a possibly-broken agent. If the package is`,
279
+ ` # intact (verify passes), start it — otherwise leave it down so the`,
280
+ ` # failure surfaces via missed heartbeats instead of a crash loop.`,
281
+ ` if (Test-PackageIntegrity) {`,
282
+ ` Log 'Package looks intact — starting minion-agent anyway.'`,
283
+ ` & $nssm start minion-agent 2>&1 | ForEach-Object { Log "nssm: $_" }`,
284
+ ` if ($wslTaskExists) {`,
285
+ ` try { schtasks /Run /TN "MinionWSL" 2>&1 | ForEach-Object { Log "schtasks: $_" } } catch {}`,
286
+ ` }`,
287
+ ` } else {`,
288
+ ` Log 'Package integrity check failed — NOT starting minion-agent. Manual recovery required.'`,
289
+ ` }`,
156
290
  `} finally {`,
157
291
  ` Log 'Cleaning up updater service...'`,
158
292
  ` & $nssm stop minion-update confirm 2>$null`,