gitdone-agent 0.6.15 → 0.6.16

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.
Files changed (2) hide show
  1. package/index.js +52 -5
  2. package/package.json +1 -1
package/index.js CHANGED
@@ -27,7 +27,7 @@ import { randomUUID } from 'node:crypto'
27
27
  // Reported to the server on every sync so the web UI can flag outdated agents.
28
28
  // Keep in lockstep with packages/agent/package.json "version" AND
29
29
  // src/lib/agentVersion.ts LATEST_AGENT_VERSION.
30
- const AGENT_VERSION = '0.6.15'
30
+ const AGENT_VERSION = '0.6.16'
31
31
 
32
32
  const AGENT_DIR = join(homedir(), '.gitdone-agent')
33
33
  const CONFIG_PATH = join(AGENT_DIR, 'config.json')
@@ -186,10 +186,52 @@ function findClaude() {
186
186
  return { path: 'claude', shell: true, found: false }
187
187
  }
188
188
 
189
+ // ─── Single instance (gd-407) ─────────────────────────────────────────────────
190
+ // Two agents on one machine (an old one surviving an update, or a manual
191
+ // `npx gitdone-agent` next to the autostarted one) flap the machine's online
192
+ // state and race dispatches. The running agent claims the machine via a pid
193
+ // file; a newcomer that finds a LIVE agent behind it exits with EXIT_DUPLICATE,
194
+ // which the supervisor treats as "do not restart me". First one wins.
195
+ // 86 is outside node's own exit-code range (1-13) — keep in lockstep with the
196
+ // run-agent.cmd template in installStartup().
197
+ const EXIT_DUPLICATE = 86
198
+ const PID_PATH = join(AGENT_DIR, 'agent.pid')
199
+
200
+ // Command line of a live process, '' when unreadable (dead process, no rights).
201
+ function processCmdline(pid) {
202
+ try {
203
+ if (process.platform === 'win32') {
204
+ return execSync(
205
+ `powershell -NoProfile -NonInteractive -Command "(Get-CimInstance Win32_Process -Filter 'ProcessId=${pid}').CommandLine"`,
206
+ { encoding: 'utf8' },
207
+ ).trim()
208
+ }
209
+ return execSync(`ps -p ${pid} -o command=`, { encoding: 'utf8' }).trim()
210
+ } catch { return '' }
211
+ }
212
+
213
+ function ensureSingleInstance() {
214
+ try {
215
+ const pid = parseInt(readFileSync(PID_PATH, 'utf8'), 10)
216
+ if (pid && pid !== process.pid) {
217
+ process.kill(pid, 0) // throws when that pid is no longer alive
218
+ // PID reuse guard: only defer to a process that really looks like an
219
+ // agent (stable copy agent.mjs, or any gitdone-agent npx/global run).
220
+ if (/gitdone-agent|agent\.mjs/i.test(processCmdline(pid))) {
221
+ log(`✗ друг gitdone-agent вече върви (PID ${pid}) — този процес излиза, за да няма два агента на машината`)
222
+ process.exit(EXIT_DUPLICATE)
223
+ }
224
+ }
225
+ } catch { /* no/stale pid file → the machine is free, we take over */ }
226
+ try { ensureAgentDir(); writeFileSync(PID_PATH, String(process.pid)) } catch { /* best-effort */ }
227
+ }
228
+
189
229
  // Kill any previously-running agent processes so an update applies WITHOUT a
190
- // Windows re-login. Targets node processes launched from the agent dir
191
- // (~/.gitdone-agent), excluding ourselves the npx installer runs from the
192
- // npx cache, so it won't match. Best-effort; failures are non-fatal.
230
+ // Windows re-login. Targets node processes that look like a gitdone agent
231
+ // both the stable copy in ~/.gitdone-agent AND one running straight from the
232
+ // npx cache / global install (its command line contains "gitdone-agent") —
233
+ // excluding ourselves and our parent (the npx wrapper that launched us).
234
+ // Best-effort; failures are non-fatal.
193
235
  function stopRunningAgents() {
194
236
  // Kill the supervisor loops (cmd.exe running run-agent.cmd) FIRST, then the
195
237
  // agents — the other order lets a still-alive supervisor immediately respawn
@@ -200,7 +242,7 @@ function stopRunningAgents() {
200
242
  " Where-Object { $_.CommandLine -like '*run-agent.cmd*' } |",
201
243
  ' ForEach-Object { Stop-Process -Id $_.ProcessId -Force }',
202
244
  "Get-CimInstance Win32_Process -Filter \"Name='node.exe'\" |",
203
- ` Where-Object { $_.CommandLine -like '*.gitdone-agent*' -and $_.ProcessId -ne ${process.pid} } |`,
245
+ ` Where-Object { ($_.CommandLine -like '*gitdone-agent*' -or $_.CommandLine -like '*agent.mjs*') -and $_.ProcessId -ne ${process.pid} -and $_.ProcessId -ne ${process.ppid || 0} } |`,
204
246
  ' ForEach-Object { Stop-Process -Id $_.ProcessId -Force }',
205
247
  ].join('\n')
206
248
  try {
@@ -254,12 +296,16 @@ function installStartup() {
254
296
  `"%NODE%" "${agentPath}" 2>> "${crashLog}"`,
255
297
  'set CODE=%errorlevel%',
256
298
  `echo [%date% %time%] agent exited (code %CODE%) - restart in 10s >> "${crashLog}"`,
299
+ 'if "%CODE%"=="86" goto duplicate',
257
300
  'if "%CODE%"=="-1073741502" goto dead',
258
301
  'if "%CODE%"=="-1073741515" goto dead',
259
302
  'ping -n 11 127.0.0.1 >nul 2>nul || goto dead',
260
303
  'goto loop',
261
304
  ':dead',
262
305
  `echo [%date% %time%] node cannot start (code %CODE%) - supervisor giving up until next login >> "${crashLog}"`,
306
+ 'exit /b',
307
+ ':duplicate',
308
+ `echo [%date% %time%] another agent already runs this machine (code 86) - this supervisor exits >> "${crashLog}"`,
263
309
  ].join('\r\n')
264
310
  const cmdPath = join(AGENT_DIR, 'run-agent.cmd')
265
311
  writeFileSync(cmdPath, cmd, 'utf8')
@@ -1603,6 +1649,7 @@ async function streamCommands(cfg) {
1603
1649
  // ─── Main ────────────────────────────────────────────────────────────────────
1604
1650
 
1605
1651
  async function runLoop(cfg) {
1652
+ ensureSingleInstance()
1606
1653
  log(`gitdone-agent started — machine: ${cfg.hostname}, server: ${cfg.url}, interval: ${cfg.interval}s`)
1607
1654
  log(` roots: ${cfg.roots.join(', ') || '(none — add one with --root)'}`)
1608
1655
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gitdone-agent",
3
- "version": "0.6.15",
3
+ "version": "0.6.16",
4
4
  "description": "Local git agent for gitdone — watches a local repo and sends snapshots to gitdone.eu",
5
5
  "type": "module",
6
6
  "bin": {