@pushary/agent-hooks 0.89.13 → 0.90.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/CHANGELOG.md +73 -0
- package/data/SKILL.md +1 -1
- package/data/cursor-plugin/skills/pushary/SKILL.md +1 -1
- package/data/vscode-plugin/skills/pushary/SKILL.md +1 -1
- package/dist/bin/pushary-bell-hook.js +4 -4
- package/dist/bin/pushary-bell.js +8 -8
- package/dist/bin/pushary-claude.js +72 -21
- package/dist/bin/pushary-clean.js +16 -42
- package/dist/bin/pushary-codex-bridge.js +37 -12
- package/dist/bin/pushary-codex-hook.js +45 -12
- package/dist/bin/pushary-codex.js +7 -7
- package/dist/bin/pushary-connect.js +11 -10
- package/dist/bin/pushary-daemon.js +187 -63
- package/dist/bin/pushary-disconnect.js +8 -8
- package/dist/bin/pushary-doctor.js +108 -25
- package/dist/bin/pushary-elicitation-hook.js +4 -4
- package/dist/bin/pushary-gemini-bridge.js +33 -11
- package/dist/bin/pushary-gemini-hook.js +21 -10
- package/dist/bin/pushary-hook.js +13 -13
- package/dist/bin/pushary-login.js +18 -7
- package/dist/bin/pushary-logout.js +6 -6
- package/dist/bin/pushary-mode.js +28 -21
- package/dist/bin/pushary-notification-hook.js +7 -7
- package/dist/bin/pushary-opencode-hook.js +8 -8
- package/dist/bin/pushary-permission-denied-hook.js +11 -11
- package/dist/bin/pushary-permission-hook.js +11 -11
- package/dist/bin/pushary-post-hook.js +14 -8
- package/dist/bin/pushary-prompt-hook.js +7 -7
- package/dist/bin/pushary-session-end-hook.js +7 -7
- package/dist/bin/pushary-session-start-hook.js +7 -7
- package/dist/bin/pushary-setup.js +168 -45
- package/dist/bin/pushary-stats.js +4 -4
- package/dist/bin/pushary-status.js +11 -10
- package/dist/bin/pushary-stop-hook.js +14 -8
- package/dist/bin/pushary-stopfailure-hook.js +7 -7
- package/dist/bin/pushary-suggestions.js +2 -2
- package/dist/bin/pushary-transcripts.js +6 -6
- package/dist/bin/pushary-upgrade.js +15 -15
- package/dist/bin/pushary-wait.js +5 -5
- package/dist/bin/pushary.js +1 -1
- package/dist/{chunk-B2HND22S.js → chunk-2LAHXHBR.js} +1 -1
- package/dist/{chunk-GMJDQDXU.js → chunk-43GMYSS4.js} +1 -0
- package/dist/{chunk-OX35BSPV.js → chunk-4LOPSGLP.js} +70 -11
- package/dist/{chunk-JTBDBKHU.js → chunk-54ELC7D7.js} +1 -1
- package/dist/{chunk-C56VHSXU.js → chunk-5NAZ7SFS.js} +1 -1
- package/dist/{chunk-4IPSMEGT.js → chunk-7FE2KABY.js} +12 -2
- package/dist/{chunk-RAEPAUNJ.js → chunk-7TPQEWUZ.js} +1 -1
- package/dist/{chunk-ZO43JTT3.js → chunk-AJYV3VGU.js} +1 -1
- package/dist/{chunk-QFJZHVVL.js → chunk-AZPIQG4G.js} +35 -1
- package/dist/{chunk-5MNTWAAJ.js → chunk-B6H2KAQC.js} +29 -22
- package/dist/{chunk-MNTZKFZI.js → chunk-BQBEH6TV.js} +5 -5
- package/dist/{chunk-WHDSJ6PD.js → chunk-BVBEURWJ.js} +1 -1
- package/dist/{chunk-ZWEKONU6.js → chunk-CELX75MW.js} +1 -1
- package/dist/{chunk-ZP3SNUP7.js → chunk-CNPAT3HL.js} +3 -2
- package/dist/{chunk-YXT55PYF.js → chunk-DYP6SMOR.js} +1 -1
- package/dist/chunk-DZDI62LY.js +145 -0
- package/dist/{chunk-VF3RTVWY.js → chunk-EZIEXHYM.js} +15 -7
- package/dist/{chunk-7JZCADBR.js → chunk-F2ICEWZL.js} +101 -19
- package/dist/{chunk-JHJHCMAF.js → chunk-F7OURW3C.js} +24 -6
- package/dist/{chunk-VASZ6TWW.js → chunk-FQL5E6NG.js} +2 -2
- package/dist/{chunk-JVLB5CAJ.js → chunk-GL3FFM7A.js} +7 -4
- package/dist/{chunk-5YEF55AX.js → chunk-H2QZRQR2.js} +1 -1
- package/dist/{chunk-S4BLNILH.js → chunk-I463AEG3.js} +1 -1
- package/dist/{chunk-ANMHIZZY.js → chunk-JW4KO5AF.js} +1 -1
- package/dist/{chunk-3YGGEDJK.js → chunk-KUZWXPUS.js} +3 -2
- package/dist/{chunk-N4QJ3CLX.js → chunk-L4E2BIJ2.js} +80 -7
- package/dist/{chunk-NBDU3R3G.js → chunk-OSALSIUF.js} +1 -1
- package/dist/{chunk-KNLKHJZR.js → chunk-PYA4FLKI.js} +32 -21
- package/dist/chunk-QCGNGEGE.js +15 -0
- package/dist/chunk-S6LYP7QA.js +127 -0
- package/dist/{chunk-M3UABYRT.js → chunk-UZNKVRLE.js} +1 -1
- package/dist/{chunk-FOGW6O2Y.js → chunk-VHWCVD42.js} +1 -1
- package/dist/{chunk-QINYXLA5.js → chunk-VNMKIJZB.js} +15 -2
- package/dist/{chunk-CBGGUEKE.js → chunk-Y6BWPERV.js} +90 -7
- package/dist/src/index.d.ts +3 -0
- package/dist/src/index.js +11 -11
- package/package.json +1 -1
- package/dist/chunk-MSTHT2M7.js +0 -72
|
@@ -1,38 +1,46 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
inspectConfiguredManagedProviders
|
|
4
|
-
} from "../chunk-
|
|
5
|
-
import "../chunk-
|
|
4
|
+
} from "../chunk-BQBEH6TV.js";
|
|
5
|
+
import "../chunk-H2QZRQR2.js";
|
|
6
6
|
import {
|
|
7
7
|
HEARTBEAT_INTERVAL_MS,
|
|
8
8
|
sendHeartbeat
|
|
9
|
-
} from "../chunk-
|
|
10
|
-
import "../chunk-
|
|
11
|
-
import "../chunk-
|
|
12
|
-
import "../chunk-
|
|
13
|
-
import "../chunk-
|
|
9
|
+
} from "../chunk-7TPQEWUZ.js";
|
|
10
|
+
import "../chunk-DZDI62LY.js";
|
|
11
|
+
import "../chunk-GL3FFM7A.js";
|
|
12
|
+
import "../chunk-2LAHXHBR.js";
|
|
13
|
+
import "../chunk-EZIEXHYM.js";
|
|
14
14
|
import {
|
|
15
15
|
DAEMON_LEASE_RENEW_INTERVAL_MS,
|
|
16
16
|
acquireDaemonLease,
|
|
17
|
+
clearDaemonNeedsLogin,
|
|
17
18
|
daemonServiceManagerAvailable,
|
|
19
|
+
describeDaemonLoginBlock,
|
|
18
20
|
ensureDaemonRunning,
|
|
19
21
|
hasDaemonServiceDefinition,
|
|
20
22
|
manageDaemonService,
|
|
21
23
|
parseDaemonAction,
|
|
24
|
+
readDaemonNeedsLogin,
|
|
22
25
|
readDaemonStatus,
|
|
23
26
|
replaceDaemonService,
|
|
24
27
|
requestDaemonHandoff,
|
|
25
|
-
stopDaemonRunning
|
|
26
|
-
|
|
28
|
+
stopDaemonRunning,
|
|
29
|
+
writeDaemonNeedsLogin
|
|
30
|
+
} from "../chunk-F2ICEWZL.js";
|
|
27
31
|
import {
|
|
28
32
|
detectProviderLiveCapabilities,
|
|
29
33
|
hasSpawnProvider,
|
|
30
34
|
resolveControlPolicy
|
|
31
35
|
} from "../chunk-3OXHYPZG.js";
|
|
32
36
|
import {
|
|
37
|
+
SESSION_REAP_SILENCE_MS,
|
|
38
|
+
describeIdleWindow,
|
|
33
39
|
findLiveProcessBySpawnId,
|
|
34
|
-
|
|
35
|
-
|
|
40
|
+
listProcessProofs,
|
|
41
|
+
ownsLiveProcess,
|
|
42
|
+
silentSpawnedProofs
|
|
43
|
+
} from "../chunk-Y6BWPERV.js";
|
|
36
44
|
import {
|
|
37
45
|
spawnClaude
|
|
38
46
|
} from "../chunk-XHKBHWLX.js";
|
|
@@ -41,27 +49,30 @@ import {
|
|
|
41
49
|
} from "../chunk-L4ZCFMWW.js";
|
|
42
50
|
import {
|
|
43
51
|
getMachineId
|
|
44
|
-
} from "../chunk-
|
|
45
|
-
import "../chunk-
|
|
52
|
+
} from "../chunk-54ELC7D7.js";
|
|
53
|
+
import "../chunk-KUZWXPUS.js";
|
|
54
|
+
import {
|
|
55
|
+
writeHuman
|
|
56
|
+
} from "../chunk-QCGNGEGE.js";
|
|
46
57
|
import {
|
|
47
58
|
exitOnHelpFlag
|
|
48
|
-
} from "../chunk-
|
|
49
|
-
import "../chunk-
|
|
59
|
+
} from "../chunk-BVBEURWJ.js";
|
|
60
|
+
import "../chunk-43GMYSS4.js";
|
|
50
61
|
import "../chunk-DINDL2CF.js";
|
|
51
62
|
import {
|
|
52
63
|
readControlMode
|
|
53
|
-
} from "../chunk-
|
|
64
|
+
} from "../chunk-OSALSIUF.js";
|
|
54
65
|
import "../chunk-BC3VCZ3E.js";
|
|
55
66
|
import {
|
|
56
67
|
EXIT
|
|
57
68
|
} from "../chunk-KZERVKTD.js";
|
|
58
|
-
import "../chunk-
|
|
69
|
+
import "../chunk-JW4KO5AF.js";
|
|
59
70
|
import "../chunk-4LL6LG5X.js";
|
|
60
71
|
import {
|
|
61
72
|
getApiKey,
|
|
62
73
|
getBaseUrl
|
|
63
74
|
} from "../chunk-2UMNXADU.js";
|
|
64
|
-
import "../chunk-
|
|
75
|
+
import "../chunk-VNMKIJZB.js";
|
|
65
76
|
|
|
66
77
|
// src/spawn-daemon.ts
|
|
67
78
|
import { execFileSync, spawn } from "child_process";
|
|
@@ -119,6 +130,7 @@ var VERSION_CHECK_INTERVAL_MS = 2e3;
|
|
|
119
130
|
var CONTROL_PROBE_INTERVAL_MS = 6e4;
|
|
120
131
|
var MAX_TRACKED_PROCESSES = 100;
|
|
121
132
|
var MAX_COMPLETED_STOPS = 20;
|
|
133
|
+
var SESSION_REAP_INTERVAL_MS = 6e4;
|
|
122
134
|
var controlProbeDue = (lastProbeAt, at = Date.now()) => lastProbeAt === 0 || at - lastProbeAt >= CONTROL_PROBE_INTERVAL_MS;
|
|
123
135
|
var parseControlProbe = (value) => {
|
|
124
136
|
if (typeof value !== "object" || value === null) return null;
|
|
@@ -256,6 +268,28 @@ var spawnRateAllows = (timestamps, now, maxPerWindow = MAX_SPAWNS_PER_WINDOW, wi
|
|
|
256
268
|
var stderr = (msg) => {
|
|
257
269
|
process.stderr.write(msg);
|
|
258
270
|
};
|
|
271
|
+
var log = (message) => {
|
|
272
|
+
writeHuman(`${(/* @__PURE__ */ new Date()).toISOString()} ${message}
|
|
273
|
+
`);
|
|
274
|
+
};
|
|
275
|
+
var errMsg = (err) => err instanceof Error ? err.message : String(err);
|
|
276
|
+
var trackFailureStreak = (name, sinks) => {
|
|
277
|
+
let failing = false;
|
|
278
|
+
let lastDetail = "";
|
|
279
|
+
return {
|
|
280
|
+
fail(detail) {
|
|
281
|
+
sinks.recordError(`${name}: ${detail}`);
|
|
282
|
+
if (!failing || detail !== lastDetail) sinks.log(`${name} failing: ${detail}`);
|
|
283
|
+
failing = true;
|
|
284
|
+
lastDetail = detail;
|
|
285
|
+
},
|
|
286
|
+
recover() {
|
|
287
|
+
if (failing) sinks.log(`${name} recovered`);
|
|
288
|
+
failing = false;
|
|
289
|
+
lastDetail = "";
|
|
290
|
+
}
|
|
291
|
+
};
|
|
292
|
+
};
|
|
259
293
|
var runSpawnDaemon = async () => {
|
|
260
294
|
let apiKey;
|
|
261
295
|
try {
|
|
@@ -264,6 +298,13 @@ var runSpawnDaemon = async () => {
|
|
|
264
298
|
stderr("[pushary] daemon needs an API key. Run `npx @pushary/agent-hooks setup`.\n");
|
|
265
299
|
return 1;
|
|
266
300
|
}
|
|
301
|
+
const loginBlock = readDaemonNeedsLogin();
|
|
302
|
+
if (loginBlock.kind === "blocked") {
|
|
303
|
+
stderr(`[pushary] daemon not starting: ${describeDaemonLoginBlock(loginBlock.record)}
|
|
304
|
+
`);
|
|
305
|
+
return 0;
|
|
306
|
+
}
|
|
307
|
+
if (loginBlock.kind === "stale") clearDaemonNeedsLogin();
|
|
267
308
|
const machineId = getMachineId();
|
|
268
309
|
const baseUrl = getBaseUrl();
|
|
269
310
|
const pusharyBin = resolveBinary("pushary") ?? "pushary";
|
|
@@ -288,7 +329,7 @@ var runSpawnDaemon = async () => {
|
|
|
288
329
|
}
|
|
289
330
|
const lifecycle = acquireDaemonLease({ machineId });
|
|
290
331
|
if (lifecycle.kind === "already-running") {
|
|
291
|
-
|
|
332
|
+
writeHuman(`[pushary] daemon already online as pid ${lifecycle.status.pid ?? "unknown"}
|
|
292
333
|
`);
|
|
293
334
|
return 0;
|
|
294
335
|
}
|
|
@@ -298,6 +339,9 @@ var runSpawnDaemon = async () => {
|
|
|
298
339
|
return 1;
|
|
299
340
|
}
|
|
300
341
|
const daemonLease = lifecycle.lease;
|
|
342
|
+
const sinks = { log, recordError: (message) => daemonLease.recordError(message) };
|
|
343
|
+
const drainHealth = trackFailureStreak("drain", sinks);
|
|
344
|
+
const heartbeatHealth = trackFailureStreak("heartbeat", sinks);
|
|
301
345
|
let stopped = false;
|
|
302
346
|
let idleTicks = 0;
|
|
303
347
|
let errorStreak = 0;
|
|
@@ -307,12 +351,14 @@ var runSpawnDaemon = async () => {
|
|
|
307
351
|
const completedStops = /* @__PURE__ */ new Map();
|
|
308
352
|
let timer;
|
|
309
353
|
let inflight;
|
|
310
|
-
let fatalCode = null;
|
|
311
354
|
let finish = () => {
|
|
312
355
|
};
|
|
313
356
|
let heartbeat;
|
|
314
357
|
let versionWatch;
|
|
315
358
|
let leaseRenewal;
|
|
359
|
+
let sessionReaper;
|
|
360
|
+
let reaping = false;
|
|
361
|
+
let rateCapped = false;
|
|
316
362
|
const launch = (req) => {
|
|
317
363
|
if (launchedProcesses.size >= MAX_TRACKED_PROCESSES) {
|
|
318
364
|
return Promise.reject(new Error("This daemon is already tracking 100 active sessions"));
|
|
@@ -364,8 +410,10 @@ var runSpawnDaemon = async () => {
|
|
|
364
410
|
let started = false;
|
|
365
411
|
child.once("error", (err) => {
|
|
366
412
|
if (!started) reject(err);
|
|
367
|
-
else
|
|
368
|
-
`);
|
|
413
|
+
else {
|
|
414
|
+
log(`spawn ${req.id}: launched session reported an error: ${err.message}`);
|
|
415
|
+
daemonLease.recordError(`spawn ${req.id}: ${err.message}`);
|
|
416
|
+
}
|
|
369
417
|
});
|
|
370
418
|
child.once("spawn", () => {
|
|
371
419
|
started = true;
|
|
@@ -374,8 +422,7 @@ var runSpawnDaemon = async () => {
|
|
|
374
422
|
child.once("close", () => launchedProcesses.delete(child.pid));
|
|
375
423
|
}
|
|
376
424
|
child.unref();
|
|
377
|
-
|
|
378
|
-
`);
|
|
425
|
+
log(`spawn ${req.id}: launched a phone-requested ${plan.agent} session in ${basename(cwd)} (pid ${child.pid ?? "unknown"})`);
|
|
379
426
|
resolve(typeof child.pid === "number" ? child.pid : null);
|
|
380
427
|
});
|
|
381
428
|
} catch (err) {
|
|
@@ -383,10 +430,45 @@ var runSpawnDaemon = async () => {
|
|
|
383
430
|
}
|
|
384
431
|
});
|
|
385
432
|
};
|
|
433
|
+
const kill = process.platform === "win32" ? (pid) => {
|
|
434
|
+
execFileSync("taskkill", windowsTaskkillArgs(pid), { stdio: "ignore" });
|
|
435
|
+
} : process.kill.bind(process);
|
|
436
|
+
const stopDeps = {
|
|
437
|
+
kill,
|
|
438
|
+
ownsProcess: (request) => {
|
|
439
|
+
const launched = request.spawnId ? launchedProcesses.get(request.pid) === request.spawnId : !request.id && launchedProcesses.has(request.pid);
|
|
440
|
+
return launched || ownsLiveProcess(request);
|
|
441
|
+
},
|
|
442
|
+
selfPid: process.pid,
|
|
443
|
+
sleep: (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
|
|
444
|
+
isAlive: (pid) => {
|
|
445
|
+
try {
|
|
446
|
+
process.kill(pid, 0);
|
|
447
|
+
return true;
|
|
448
|
+
} catch (err) {
|
|
449
|
+
return err.code === "EPERM";
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
};
|
|
453
|
+
const reapSilentSessions = async () => {
|
|
454
|
+
if (stopped || reaping) return;
|
|
455
|
+
reaping = true;
|
|
456
|
+
try {
|
|
457
|
+
const now = Date.now();
|
|
458
|
+
const silent = silentSpawnedProofs(listProcessProofs({ reapDead: true }), now, SESSION_REAP_SILENCE_MS);
|
|
459
|
+
for (const proof of silent) {
|
|
460
|
+
if (stopped) return;
|
|
461
|
+
const outcome = await applyStopAndVerify(
|
|
462
|
+
{ sessionId: proof.sessionId, pid: proof.pid, spawnId: proof.spawnId },
|
|
463
|
+
stopDeps
|
|
464
|
+
);
|
|
465
|
+
log(`session ${proof.sessionId} (pid ${proof.pid}) was silent for ${describeIdleWindow(now - proof.lastSeenAt)}: ${outcome}`);
|
|
466
|
+
}
|
|
467
|
+
} finally {
|
|
468
|
+
reaping = false;
|
|
469
|
+
}
|
|
470
|
+
};
|
|
386
471
|
const runStop = async (stop) => {
|
|
387
|
-
const kill = process.platform === "win32" ? (pid) => {
|
|
388
|
-
execFileSync("taskkill", windowsTaskkillArgs(pid), { stdio: "ignore" });
|
|
389
|
-
} : process.kill.bind(process);
|
|
390
472
|
const outcome = stop.id ? completedStops.get(stop.id) ?? await apply() : await apply();
|
|
391
473
|
if (stop.id && !completedStops.has(stop.id)) {
|
|
392
474
|
if (completedStops.size === MAX_COMPLETED_STOPS) {
|
|
@@ -402,8 +484,7 @@ var runSpawnDaemon = async () => {
|
|
|
402
484
|
refused: `could not stop session ${stop.sessionId}, the system refused the signal`,
|
|
403
485
|
skipped_self: `refused to stop session ${stop.sessionId}: that pid is this daemon`
|
|
404
486
|
};
|
|
405
|
-
|
|
406
|
-
`);
|
|
487
|
+
log(said[outcome]);
|
|
407
488
|
const acknowledgement = stopAcknowledgement(stop, machineId, outcome);
|
|
408
489
|
if (acknowledgement) {
|
|
409
490
|
for (let attempt = 0; attempt < 3 && !stopped; attempt++) {
|
|
@@ -421,23 +502,7 @@ var runSpawnDaemon = async () => {
|
|
|
421
502
|
}
|
|
422
503
|
}
|
|
423
504
|
async function apply() {
|
|
424
|
-
return applyStopAndVerify(stop,
|
|
425
|
-
kill,
|
|
426
|
-
ownsProcess: (request) => {
|
|
427
|
-
const launched = request.spawnId ? launchedProcesses.get(request.pid) === request.spawnId : !request.id && launchedProcesses.has(request.pid);
|
|
428
|
-
return launched || ownsLiveProcess(request);
|
|
429
|
-
},
|
|
430
|
-
selfPid: process.pid,
|
|
431
|
-
sleep: (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
|
|
432
|
-
isAlive: (pid) => {
|
|
433
|
-
try {
|
|
434
|
-
process.kill(pid, 0);
|
|
435
|
-
return true;
|
|
436
|
-
} catch (err) {
|
|
437
|
-
return err.code === "EPERM";
|
|
438
|
-
}
|
|
439
|
-
}
|
|
440
|
-
});
|
|
505
|
+
return applyStopAndVerify(stop, stopDeps);
|
|
441
506
|
}
|
|
442
507
|
};
|
|
443
508
|
const acknowledgeControlProbe = async (probeId) => {
|
|
@@ -491,20 +556,20 @@ var runSpawnDaemon = async () => {
|
|
|
491
556
|
signal: controller.signal
|
|
492
557
|
});
|
|
493
558
|
if (response.ok) return;
|
|
494
|
-
const error = new Error(`spawn acknowledgement failed (${response.status})`);
|
|
559
|
+
const error = new Error(`spawn ${body.spawnId} acknowledgement failed (${response.status})`);
|
|
495
560
|
if (response.status < 500) {
|
|
496
561
|
lastError = error;
|
|
497
562
|
break;
|
|
498
563
|
}
|
|
499
564
|
lastError = error;
|
|
500
565
|
} catch (err) {
|
|
501
|
-
lastError = err;
|
|
566
|
+
lastError = new Error(`spawn ${body.spawnId} acknowledgement failed: ${errMsg(err)}`);
|
|
502
567
|
} finally {
|
|
503
568
|
clearTimeout(timeout);
|
|
504
569
|
if (inflight === controller) inflight = void 0;
|
|
505
570
|
}
|
|
506
571
|
}
|
|
507
|
-
throw lastError ?? new Error(
|
|
572
|
+
throw lastError ?? new Error(`spawn ${body.spawnId} acknowledgement stopped`);
|
|
508
573
|
};
|
|
509
574
|
const launchClaim = (request) => launchSpawnClaim(request, {
|
|
510
575
|
machineId,
|
|
@@ -516,8 +581,10 @@ var runSpawnDaemon = async () => {
|
|
|
516
581
|
},
|
|
517
582
|
launch,
|
|
518
583
|
acknowledge,
|
|
519
|
-
reportLaunchError: (message) =>
|
|
520
|
-
`)
|
|
584
|
+
reportLaunchError: (message) => {
|
|
585
|
+
log(`spawn ${request.id}: could not launch session: ${message}`);
|
|
586
|
+
daemonLease.recordError(`spawn ${request.id}: ${message}`);
|
|
587
|
+
}
|
|
521
588
|
});
|
|
522
589
|
const nextDelay = () => {
|
|
523
590
|
if (errorStreak > 0) return Math.min(SLOW_POLL_MS * 2 ** (errorStreak - 1), MAX_ERROR_BACKOFF_MS);
|
|
@@ -531,9 +598,12 @@ var runSpawnDaemon = async () => {
|
|
|
531
598
|
return;
|
|
532
599
|
}
|
|
533
600
|
if (!spawnRateAllows(spawnTimestamps, Date.now())) {
|
|
601
|
+
if (!rateCapped) log(`spawn rate cap reached (${MAX_SPAWNS_PER_WINDOW} in ${SPAWN_WINDOW_MS / 1e3}s); leaving the queue until the window frees`);
|
|
602
|
+
rateCapped = true;
|
|
534
603
|
timer = setTimeout(tick, jitter(nextDelay()));
|
|
535
604
|
return;
|
|
536
605
|
}
|
|
606
|
+
rateCapped = false;
|
|
537
607
|
const controller = new AbortController();
|
|
538
608
|
inflight = controller;
|
|
539
609
|
const timeout = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
|
|
@@ -544,6 +614,7 @@ var runSpawnDaemon = async () => {
|
|
|
544
614
|
return;
|
|
545
615
|
}
|
|
546
616
|
errorStreak = 0;
|
|
617
|
+
drainHealth.recover();
|
|
547
618
|
if (drained.request) {
|
|
548
619
|
idleTicks = 0;
|
|
549
620
|
await launchClaim(drained.request);
|
|
@@ -557,14 +628,15 @@ var runSpawnDaemon = async () => {
|
|
|
557
628
|
} catch (err) {
|
|
558
629
|
if (isAuthFailure(err)) {
|
|
559
630
|
const status = err.status;
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
status === 401 ? "
|
|
631
|
+
writeDaemonNeedsLogin(status === 401 ? "key_rejected" : "plan_inactive");
|
|
632
|
+
log(
|
|
633
|
+
status === 401 ? "daemon stopping: this machine's key was rejected. Run `pushary login` to sign in again." : "daemon stopping: this workspace has no active plan, so the queue is closed. Pick one at pushary.com/dashboard/settings?tab=billing"
|
|
563
634
|
);
|
|
564
635
|
finish();
|
|
565
636
|
return;
|
|
566
637
|
}
|
|
567
638
|
errorStreak = Math.min(errorStreak + 1, MAX_ERROR_STREAK);
|
|
639
|
+
drainHealth.fail(errMsg(err));
|
|
568
640
|
} finally {
|
|
569
641
|
clearTimeout(timeout);
|
|
570
642
|
inflight = void 0;
|
|
@@ -578,12 +650,12 @@ var runSpawnDaemon = async () => {
|
|
|
578
650
|
if (heartbeat) clearInterval(heartbeat);
|
|
579
651
|
if (versionWatch) clearInterval(versionWatch);
|
|
580
652
|
if (leaseRenewal) clearInterval(leaseRenewal);
|
|
653
|
+
if (sessionReaper) clearInterval(sessionReaper);
|
|
581
654
|
inflight?.abort();
|
|
582
655
|
try {
|
|
583
656
|
daemonLease.release();
|
|
584
657
|
} catch (err) {
|
|
585
|
-
|
|
586
|
-
`);
|
|
658
|
+
log(`daemon could not release its local lease: ${errMsg(err)}`);
|
|
587
659
|
}
|
|
588
660
|
};
|
|
589
661
|
const beat = async () => {
|
|
@@ -601,10 +673,14 @@ var runSpawnDaemon = async () => {
|
|
|
601
673
|
...providerCapabilities
|
|
602
674
|
]
|
|
603
675
|
});
|
|
604
|
-
if (result.kind === "ok")
|
|
676
|
+
if (result.kind === "ok") {
|
|
677
|
+
daemonLease.recordSuccessfulHeartbeat();
|
|
678
|
+
heartbeatHealth.recover();
|
|
679
|
+
}
|
|
680
|
+
if (result.kind === "failed") heartbeatHealth.fail(result.detail);
|
|
605
681
|
if (result.kind === "unauthorized" && !stopped) {
|
|
606
|
-
|
|
607
|
-
|
|
682
|
+
writeDaemonNeedsLogin("key_rejected");
|
|
683
|
+
log("daemon stopping: this machine's key was rejected. Run `pushary login` to sign in again.");
|
|
608
684
|
finish();
|
|
609
685
|
}
|
|
610
686
|
};
|
|
@@ -614,9 +690,8 @@ var runSpawnDaemon = async () => {
|
|
|
614
690
|
versionWatch = setInterval(() => {
|
|
615
691
|
if (daemonLease.shouldYield()) finish();
|
|
616
692
|
}, VERSION_CHECK_INTERVAL_MS);
|
|
617
|
-
|
|
618
|
-
` : `
|
|
619
|
-
`);
|
|
693
|
+
sessionReaper = setInterval(() => void reapSilentSessions(), SESSION_REAP_INTERVAL_MS);
|
|
694
|
+
log(hasSpawnProvider(providerCapabilities) ? `daemon online (pid ${process.pid}): this machine (${machineId}) can now be sent a new session from your phone. Ctrl-C to stop.` : `daemon online (pid ${process.pid}): no supported Claude, Codex, or Gemini CLI was found, so phone-start is unavailable. Ctrl-C to stop.`);
|
|
620
695
|
timer = setTimeout(tick, 0);
|
|
621
696
|
const signals = daemonSignals();
|
|
622
697
|
await new Promise((resolve) => {
|
|
@@ -628,7 +703,51 @@ var runSpawnDaemon = async () => {
|
|
|
628
703
|
resolve();
|
|
629
704
|
};
|
|
630
705
|
});
|
|
631
|
-
return
|
|
706
|
+
return 0;
|
|
707
|
+
};
|
|
708
|
+
|
|
709
|
+
// src/daemon-sessions.ts
|
|
710
|
+
import { execFileSync as execFileSync2 } from "child_process";
|
|
711
|
+
var readResidentSetKb = (pid) => {
|
|
712
|
+
if (process.platform === "win32") return null;
|
|
713
|
+
try {
|
|
714
|
+
const output = execFileSync2("ps", ["-o", "rss=", "-p", String(pid)], {
|
|
715
|
+
encoding: "utf8",
|
|
716
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
717
|
+
});
|
|
718
|
+
const kb = Number.parseInt(output.trim(), 10);
|
|
719
|
+
return Number.isFinite(kb) ? kb : null;
|
|
720
|
+
} catch {
|
|
721
|
+
return null;
|
|
722
|
+
}
|
|
723
|
+
};
|
|
724
|
+
var formatAge = (ms) => {
|
|
725
|
+
const totalSeconds = Math.max(0, Math.floor(ms / 1e3));
|
|
726
|
+
const hours = Math.floor(totalSeconds / 3600);
|
|
727
|
+
const minutes = Math.floor(totalSeconds % 3600 / 60);
|
|
728
|
+
if (hours > 0) return `${hours}h${String(minutes).padStart(2, "0")}m`;
|
|
729
|
+
if (minutes > 0) return `${minutes}m`;
|
|
730
|
+
return `${totalSeconds}s`;
|
|
731
|
+
};
|
|
732
|
+
var formatMemory = (kb) => kb === null ? "n/a" : kb >= 1024 ? `${Math.round(kb / 1024)} MB` : `${kb} KB`;
|
|
733
|
+
var describeSessionProof = (entry, now, rssKb) => [
|
|
734
|
+
entry.sessionId,
|
|
735
|
+
`pid ${entry.pid}`,
|
|
736
|
+
entry.alive ? "alive" : "dead, reaped",
|
|
737
|
+
`age ${formatAge(now - entry.startedAt)}`,
|
|
738
|
+
`seen ${formatAge(now - entry.lastSeenAt)} ago`,
|
|
739
|
+
`rss ${formatMemory(rssKb)}`,
|
|
740
|
+
...entry.spawnId ? [`spawn ${entry.spawnId}`] : []
|
|
741
|
+
].join(" ");
|
|
742
|
+
var describeSessionProofs = (entries, now, residentSet) => {
|
|
743
|
+
const live = entries.filter((entry) => entry.alive).length;
|
|
744
|
+
const reaped = entries.length - live;
|
|
745
|
+
if (entries.length === 0) return ["Sessions: none"];
|
|
746
|
+
const header = `Sessions: ${live} live${reaped > 0 ? `, ${reaped} dead proof${reaped === 1 ? "" : "s"} reaped` : ""}`;
|
|
747
|
+
return [
|
|
748
|
+
header,
|
|
749
|
+
...entries.map((entry) => ` ${describeSessionProof(entry, now, entry.alive ? residentSet(entry.pid) : null)}`)
|
|
750
|
+
];
|
|
632
751
|
};
|
|
633
752
|
|
|
634
753
|
// bin/pushary-daemon.ts
|
|
@@ -650,6 +769,11 @@ var main = async () => {
|
|
|
650
769
|
if (action === "status") {
|
|
651
770
|
const status = readDaemonStatus();
|
|
652
771
|
process.stdout.write(`Pushary daemon: ${status.kind}${status.pid ? ` (pid ${status.pid})` : ""}
|
|
772
|
+
`);
|
|
773
|
+
if (status.lastError) process.stdout.write(`Last error: ${status.lastError.message} at ${status.lastError.at}
|
|
774
|
+
`);
|
|
775
|
+
const sessions = describeSessionProofs(listProcessProofs({ reapDead: true }), Date.now(), readResidentSetKb);
|
|
776
|
+
process.stdout.write(`${sessions.join("\n")}
|
|
653
777
|
`);
|
|
654
778
|
return status.kind === "running" ? EXIT.OK : EXIT.UNREACHABLE;
|
|
655
779
|
}
|
|
@@ -8,16 +8,16 @@ import {
|
|
|
8
8
|
openCodePluginDir,
|
|
9
9
|
openCodePluginFile,
|
|
10
10
|
removeOpenCodeMcpServer
|
|
11
|
-
} from "../chunk-
|
|
11
|
+
} from "../chunk-CELX75MW.js";
|
|
12
12
|
import {
|
|
13
13
|
isClaudeMcpSnapshot,
|
|
14
14
|
removeClaudeMcpServers,
|
|
15
15
|
removeGeminiSettings,
|
|
16
16
|
removePusharySettings,
|
|
17
17
|
restoreClaudeMcpServer
|
|
18
|
-
} from "../chunk-
|
|
19
|
-
import "../chunk-
|
|
20
|
-
import "../chunk-
|
|
18
|
+
} from "../chunk-GL3FFM7A.js";
|
|
19
|
+
import "../chunk-2LAHXHBR.js";
|
|
20
|
+
import "../chunk-EZIEXHYM.js";
|
|
21
21
|
import "../chunk-L4ZCFMWW.js";
|
|
22
22
|
import {
|
|
23
23
|
claudeJson,
|
|
@@ -32,14 +32,14 @@ import {
|
|
|
32
32
|
geminiSettings,
|
|
33
33
|
removeCodexHooks,
|
|
34
34
|
removeCodexSettings
|
|
35
|
-
} from "../chunk-
|
|
35
|
+
} from "../chunk-KUZWXPUS.js";
|
|
36
36
|
import {
|
|
37
37
|
exitOnHelpFlag
|
|
38
|
-
} from "../chunk-
|
|
39
|
-
import "../chunk-
|
|
38
|
+
} from "../chunk-BVBEURWJ.js";
|
|
39
|
+
import "../chunk-43GMYSS4.js";
|
|
40
40
|
import "../chunk-DINDL2CF.js";
|
|
41
41
|
import "../chunk-KZERVKTD.js";
|
|
42
|
-
import "../chunk-
|
|
42
|
+
import "../chunk-VNMKIJZB.js";
|
|
43
43
|
|
|
44
44
|
// bin/pushary-disconnect.ts
|
|
45
45
|
import { readdirSync, readFileSync, rmSync, writeFileSync } from "fs";
|