feat(update): opt-in background auto-update (AGENTHUB_AUTO_UPDATE)
Set AGENTHUB_AUTO_UPDATE=1 and the install keeps itself current with no manual `agenthub update` — ideal for the Windows client. maybeNotifyUpdate now: - auto mode: refreshes the behind-count hourly (vs 24h) and, when behind, spawns a detached `agenthub update` (fetch → reset → install → build) that doesn't block the current command; the next invocation runs the new version. A 10-min cooldown stamp prevents a second build spawning while one is in flight. - otherwise: unchanged "run `agenthub update`" hint. Safe to enable anywhere: `update` no-ops when current and refuses to discard unpushed local commits, so the authoring machine is protected. Cross-platform (no OS scheduler needed). Bump 0.2.1 -> 0.2.2. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
353c139796
commit
b0973a878c
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "agenthub",
|
||||
"version": "0.2.1",
|
||||
"version": "0.2.2",
|
||||
"description": "Local coordination layer for AI coding agents",
|
||||
"type": "module",
|
||||
"main": "./dist/index.js",
|
||||
|
||||
@ -5,6 +5,17 @@ import { dirname, join, parse } from 'node:path';
|
||||
import { homedir } from 'node:os';
|
||||
|
||||
const CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000;
|
||||
// In auto-update mode we refresh more often so a freshly pushed version is
|
||||
// picked up within the hour, and we cool down between background updates so we
|
||||
// never spawn a second reset+build while one is still running.
|
||||
const AUTO_CHECK_INTERVAL_MS = 60 * 60 * 1000;
|
||||
const AUTO_UPDATE_COOLDOWN_MS = 10 * 60 * 1000;
|
||||
|
||||
/** Opt-in: set AGENTHUB_AUTO_UPDATE=1 to self-update in the background. */
|
||||
function autoUpdateEnabled(): boolean {
|
||||
const v = process.env.AGENTHUB_AUTO_UPDATE;
|
||||
return !!v && v !== '0' && v.toLowerCase() !== 'false';
|
||||
}
|
||||
|
||||
/** Locate the agenthub package install dir by walking up from this file. */
|
||||
function findPackageRoot(): string | undefined {
|
||||
@ -118,15 +129,50 @@ export async function update(rootOverride?: string): Promise<void> {
|
||||
console.log(`AgentHub updated: ${before} → ${packageVersion(root)} (now at ${gitCapture(root, ['rev-parse', '--short', 'HEAD'])}).`);
|
||||
}
|
||||
|
||||
function checkStampPath(): string {
|
||||
return join(homedir(), '.agenthub', 'last-update-check');
|
||||
function stampPath(name: string): string {
|
||||
return join(homedir(), '.agenthub', name);
|
||||
}
|
||||
|
||||
function readStamp(path: string): number {
|
||||
try {
|
||||
return Number(readFileSync(path, 'utf-8')) || 0;
|
||||
} catch {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
function writeStamp(path: string): void {
|
||||
try {
|
||||
mkdirSync(dirname(path), { recursive: true });
|
||||
writeFileSync(path, String(Date.now()), 'utf-8');
|
||||
} catch {
|
||||
// best-effort
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Non-blocking, throttled update hint. Prints a one-line notice to stderr when
|
||||
* the install is behind its remote, then kicks off a detached background fetch
|
||||
* so the next run is accurate. Never throws — a failed check must never break
|
||||
* a command.
|
||||
* Spawn a detached `agenthub update` that fetches + (if behind) resets,
|
||||
* reinstalls and rebuilds — without blocking the current command. The next
|
||||
* invocation runs the new version. `update` no-ops when already current and
|
||||
* refuses to discard unpushed local commits, so this is safe to fire
|
||||
* automatically (e.g. on the authoring machine).
|
||||
*/
|
||||
function spawnBackgroundUpdate(root: string): void {
|
||||
try {
|
||||
const entry = join(root, 'bin', 'agenthub.js');
|
||||
spawn(process.execPath, [entry, 'update'], { cwd: root, detached: true, stdio: 'ignore' }).unref();
|
||||
} catch {
|
||||
// best-effort
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Non-blocking, throttled update check. Kicks off a detached background fetch so
|
||||
* the behind-count stays fresh, then either:
|
||||
* - AGENTHUB_AUTO_UPDATE set → self-updates in the background when behind
|
||||
* (cooled down so concurrent builds can't pile up); or
|
||||
* - otherwise → prints a one-line "run `agenthub update`" hint.
|
||||
* Never throws — a failed check must never break a command.
|
||||
*/
|
||||
export function maybeNotifyUpdate(rootOverride?: string): void {
|
||||
try {
|
||||
@ -134,18 +180,13 @@ export function maybeNotifyUpdate(rootOverride?: string): void {
|
||||
if (!root || !existsSync(join(root, '.git'))) return;
|
||||
|
||||
const branch = currentBranch(root);
|
||||
const auto = autoUpdateEnabled();
|
||||
|
||||
const stamp = checkStampPath();
|
||||
let last = 0;
|
||||
const stamp = stampPath('last-update-check');
|
||||
const interval = auto ? AUTO_CHECK_INTERVAL_MS : CHECK_INTERVAL_MS;
|
||||
if (Date.now() - readStamp(stamp) > interval) {
|
||||
writeStamp(stamp);
|
||||
try {
|
||||
last = Number(readFileSync(stamp, 'utf-8')) || 0;
|
||||
} catch {
|
||||
// no stamp yet
|
||||
}
|
||||
if (Date.now() - last > CHECK_INTERVAL_MS) {
|
||||
try {
|
||||
mkdirSync(dirname(stamp), { recursive: true });
|
||||
writeFileSync(stamp, String(Date.now()), 'utf-8');
|
||||
spawn('git', ['fetch', '--quiet', 'origin', branch], { cwd: root, detached: true, stdio: 'ignore' }).unref();
|
||||
} catch {
|
||||
// best-effort background refresh
|
||||
@ -158,7 +199,18 @@ export function maybeNotifyUpdate(rootOverride?: string): void {
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
if (behind !== '0') {
|
||||
if (behind === '0') return;
|
||||
|
||||
if (auto) {
|
||||
// Only one background update per cooldown window — a second command while
|
||||
// the reset+build is in flight must not spawn a competing update.
|
||||
const autoStamp = stampPath('last-auto-update');
|
||||
if (Date.now() - readStamp(autoStamp) > AUTO_UPDATE_COOLDOWN_MS) {
|
||||
writeStamp(autoStamp);
|
||||
process.stderr.write(`\n🔄 AgentHub: updating in background (${behind} commit(s) behind)…\n\n`);
|
||||
spawnBackgroundUpdate(root);
|
||||
}
|
||||
} else {
|
||||
process.stderr.write(`\n🔄 A newer AgentHub is available (${behind} commit(s) behind). Run \`agenthub update\`.\n\n`);
|
||||
}
|
||||
} catch {
|
||||
|
||||
@ -112,7 +112,7 @@ async function runRemote(serverUrl: string, fn: () => Promise<void>): Promise<vo
|
||||
export function createProgram(cwd: string): Command {
|
||||
const program = new Command('agenthub')
|
||||
.description('Local coordination layer for AI coding agents')
|
||||
.version('0.2.1')
|
||||
.version('0.2.2')
|
||||
.option('--server <url>', 'AgentHub server URL (env: AGENTHUB_SERVER)');
|
||||
|
||||
program
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user