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",
|
"name": "agenthub",
|
||||||
"version": "0.2.1",
|
"version": "0.2.2",
|
||||||
"description": "Local coordination layer for AI coding agents",
|
"description": "Local coordination layer for AI coding agents",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "./dist/index.js",
|
"main": "./dist/index.js",
|
||||||
|
|||||||
@ -5,6 +5,17 @@ import { dirname, join, parse } from 'node:path';
|
|||||||
import { homedir } from 'node:os';
|
import { homedir } from 'node:os';
|
||||||
|
|
||||||
const CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000;
|
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. */
|
/** Locate the agenthub package install dir by walking up from this file. */
|
||||||
function findPackageRoot(): string | undefined {
|
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'])}).`);
|
console.log(`AgentHub updated: ${before} → ${packageVersion(root)} (now at ${gitCapture(root, ['rev-parse', '--short', 'HEAD'])}).`);
|
||||||
}
|
}
|
||||||
|
|
||||||
function checkStampPath(): string {
|
function stampPath(name: string): string {
|
||||||
return join(homedir(), '.agenthub', 'last-update-check');
|
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
|
* Spawn a detached `agenthub update` that fetches + (if behind) resets,
|
||||||
* the install is behind its remote, then kicks off a detached background fetch
|
* reinstalls and rebuilds — without blocking the current command. The next
|
||||||
* so the next run is accurate. Never throws — a failed check must never break
|
* invocation runs the new version. `update` no-ops when already current and
|
||||||
* a command.
|
* 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 {
|
export function maybeNotifyUpdate(rootOverride?: string): void {
|
||||||
try {
|
try {
|
||||||
@ -134,18 +180,13 @@ export function maybeNotifyUpdate(rootOverride?: string): void {
|
|||||||
if (!root || !existsSync(join(root, '.git'))) return;
|
if (!root || !existsSync(join(root, '.git'))) return;
|
||||||
|
|
||||||
const branch = currentBranch(root);
|
const branch = currentBranch(root);
|
||||||
|
const auto = autoUpdateEnabled();
|
||||||
|
|
||||||
const stamp = checkStampPath();
|
const stamp = stampPath('last-update-check');
|
||||||
let last = 0;
|
const interval = auto ? AUTO_CHECK_INTERVAL_MS : CHECK_INTERVAL_MS;
|
||||||
|
if (Date.now() - readStamp(stamp) > interval) {
|
||||||
|
writeStamp(stamp);
|
||||||
try {
|
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();
|
spawn('git', ['fetch', '--quiet', 'origin', branch], { cwd: root, detached: true, stdio: 'ignore' }).unref();
|
||||||
} catch {
|
} catch {
|
||||||
// best-effort background refresh
|
// best-effort background refresh
|
||||||
@ -158,7 +199,18 @@ export function maybeNotifyUpdate(rootOverride?: string): void {
|
|||||||
} catch {
|
} catch {
|
||||||
return;
|
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`);
|
process.stderr.write(`\n🔄 A newer AgentHub is available (${behind} commit(s) behind). Run \`agenthub update\`.\n\n`);
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
|
|||||||
@ -112,7 +112,7 @@ async function runRemote(serverUrl: string, fn: () => Promise<void>): Promise<vo
|
|||||||
export function createProgram(cwd: string): Command {
|
export function createProgram(cwd: string): Command {
|
||||||
const program = new Command('agenthub')
|
const program = new Command('agenthub')
|
||||||
.description('Local coordination layer for AI coding agents')
|
.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)');
|
.option('--server <url>', 'AgentHub server URL (env: AGENTHUB_SERVER)');
|
||||||
|
|
||||||
program
|
program
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user