@einsia/agent-git 0.2.11 → 0.2.12
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 +9 -0
- package/npm/README.md +3 -0
- package/npm/lib/reconcile-worker.js +40 -0
- package/npm/lib/reconcile.js +51 -0
- package/npm/lib/run.js +13 -4
- package/package.json +6 -6
package/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,15 @@ its [GitHub Release](https://github.com/Einsia/agent-git/releases), and the
|
|
|
8
8
|
|
|
9
9
|
## [Unreleased]
|
|
10
10
|
|
|
11
|
+
## [0.2.12] - 2026-09-28
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
|
|
15
|
+
- Retry RC daemon reconciliation when a global npm installation's lifecycle
|
|
16
|
+
scripts are disabled. The next CLI invocation schedules the guarded handoff
|
|
17
|
+
in the background, while project-local and temporary npm packages cannot
|
|
18
|
+
replace a persistent daemon.
|
|
19
|
+
|
|
11
20
|
## [0.2.11] - 2026-09-28
|
|
12
21
|
|
|
13
22
|
### Fixed
|
package/npm/README.md
CHANGED
|
@@ -65,6 +65,9 @@ Unsupported operating system and architecture pairs get no prebuilt binary: runn
|
|
|
65
65
|
recipe.
|
|
66
66
|
- `agit setup` failing in postinstall never blocks the install — the CLI is on
|
|
67
67
|
disk; re-run `agit setup` any time.
|
|
68
|
+
- If npm suppresses postinstall, the next invocation of a globally installed
|
|
69
|
+
`agit` schedules a guarded RC daemon reconciliation in the background.
|
|
70
|
+
npm cannot run the daemon handoff at install time when it suppresses scripts.
|
|
68
71
|
|
|
69
72
|
## Environment variables
|
|
70
73
|
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { spawnSync } = require('child_process');
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
|
|
7
|
+
const [bin, pkgRoot, launcher] = process.argv.slice(2);
|
|
8
|
+
if (!bin || !pkgRoot) process.exit(0);
|
|
9
|
+
|
|
10
|
+
// A transient npx or project-local binary cannot own a persistent daemon.
|
|
11
|
+
// npm's global root or its verified bin symlink identifies a durable install.
|
|
12
|
+
const root = spawnSync('npm', ['root', '-g'], { encoding: 'utf8', timeout: 5000, windowsHide: true });
|
|
13
|
+
let global = false;
|
|
14
|
+
try {
|
|
15
|
+
const installed = fs.realpathSync(pkgRoot);
|
|
16
|
+
const same = other => process.platform === 'win32'
|
|
17
|
+
? installed.toLowerCase() === other.toLowerCase()
|
|
18
|
+
: installed === other;
|
|
19
|
+
if (!root.error && root.status === 0) {
|
|
20
|
+
try {
|
|
21
|
+
global = same(fs.realpathSync(path.join(root.stdout.trim(), '@einsia', 'agent-git', 'npm')));
|
|
22
|
+
} catch { /* A missing current npm root may leave a still-valid installed launcher. */ }
|
|
23
|
+
}
|
|
24
|
+
if (!global && launcher && fs.lstatSync(launcher).isSymbolicLink()
|
|
25
|
+
&& path.basename(path.dirname(launcher)) === 'bin'
|
|
26
|
+
&& same(fs.realpathSync(path.join(path.dirname(launcher), '..', 'lib', 'node_modules', '@einsia', 'agent-git', 'npm')))
|
|
27
|
+
&& fs.realpathSync(launcher) === fs.realpathSync(path.join(pkgRoot, 'shim.js'))) {
|
|
28
|
+
global = true;
|
|
29
|
+
}
|
|
30
|
+
} catch {
|
|
31
|
+
process.exit(0);
|
|
32
|
+
}
|
|
33
|
+
if (!global) process.exit(0);
|
|
34
|
+
|
|
35
|
+
spawnSync(bin, ['rc', 'local', 'reconcile-installed', '--allow-other-installation'], {
|
|
36
|
+
stdio: 'ignore',
|
|
37
|
+
timeout: 35000,
|
|
38
|
+
env: { ...process.env, AGIT_INSTALL_CHANNEL: 'npm_global' },
|
|
39
|
+
windowsHide: true,
|
|
40
|
+
});
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { spawn } = require('child_process');
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const os = require('os');
|
|
6
|
+
const path = require('path');
|
|
7
|
+
|
|
8
|
+
const RETRY_MS = 5 * 60 * 1000;
|
|
9
|
+
|
|
10
|
+
// An npm policy may suppress postinstall. The next CLI use retries the guarded
|
|
11
|
+
// daemon handoff without changing the command's output or exit status.
|
|
12
|
+
function scheduleReconciliation(bin, pkgRoot) {
|
|
13
|
+
if (process.env.AGIT_BINARY || process.env.AGIT_INTERNAL_UPDATE_RESTART) return;
|
|
14
|
+
const home = process.env.AGIT_HOME?.trim() || path.join(os.homedir(), '.agit');
|
|
15
|
+
const dir = path.join(home, 'desktop-rc');
|
|
16
|
+
let fingerprint;
|
|
17
|
+
try {
|
|
18
|
+
const daemon = fs.statSync(path.join(dir, 'agitd.pid'));
|
|
19
|
+
const installed = fs.statSync(bin);
|
|
20
|
+
fingerprint = `${path.resolve(bin)}:${installed.size}:${installed.mtimeMs}:${daemon.size}:${daemon.mtimeMs}`;
|
|
21
|
+
const stamp = path.join(dir, 'npm-reconcile.json');
|
|
22
|
+
try {
|
|
23
|
+
const previous = JSON.parse(fs.readFileSync(stamp, 'utf8'));
|
|
24
|
+
if (previous.fingerprint === fingerprint && Date.now() - previous.at < RETRY_MS) return;
|
|
25
|
+
} catch { /* An absent or invalid retry receipt cannot suppress reconciliation. */ }
|
|
26
|
+
const temporary = `${stamp}.${process.pid}.${Math.random().toString(16).slice(2)}`;
|
|
27
|
+
try {
|
|
28
|
+
fs.writeFileSync(temporary, JSON.stringify({ fingerprint, at: Date.now() }), { mode: 0o600, flag: 'wx' });
|
|
29
|
+
fs.renameSync(temporary, stamp);
|
|
30
|
+
} catch {
|
|
31
|
+
try { fs.unlinkSync(temporary); } catch { /* The best-effort receipt is optional. */ }
|
|
32
|
+
}
|
|
33
|
+
} catch {
|
|
34
|
+
return; // No running daemon is indicated in this home.
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
try {
|
|
38
|
+
const child = spawn(process.execPath, [path.join(__dirname, 'reconcile-worker.js'), bin, pkgRoot, process.argv[1] || ''], {
|
|
39
|
+
detached: true,
|
|
40
|
+
stdio: 'ignore',
|
|
41
|
+
env: { ...process.env, AGIT_TELEMETRY_DEFER: '1', AGIT_INTERNAL_UPDATE_RESTART: '1' },
|
|
42
|
+
windowsHide: true,
|
|
43
|
+
});
|
|
44
|
+
child.on('error', () => {});
|
|
45
|
+
child.unref();
|
|
46
|
+
} catch {
|
|
47
|
+
// Reconciliation never changes the forwarded command's result.
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
module.exports = { scheduleReconciliation };
|
package/npm/lib/run.js
CHANGED
|
@@ -5,10 +5,10 @@ const path = require('path');
|
|
|
5
5
|
const platform = require('./platform');
|
|
6
6
|
const { resolveBinary } = require('./resolve');
|
|
7
7
|
const log = require('./log');
|
|
8
|
+
const { scheduleReconciliation } = require('./reconcile');
|
|
8
9
|
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
// codes and stdio pass-through are load-bearing semantics and must not change.
|
|
10
|
+
// Forward argv, stdio, and exit status unchanged. Hooks rely on those semantics;
|
|
11
|
+
// daemon reconciliation therefore runs independently of the forwarded command.
|
|
12
12
|
function run() {
|
|
13
13
|
const pkgRoot = path.join(__dirname, '..');
|
|
14
14
|
let bin = null;
|
|
@@ -37,7 +37,13 @@ function run() {
|
|
|
37
37
|
process.exit(127);
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
const
|
|
40
|
+
const args = process.argv.slice(2);
|
|
41
|
+
// A bridge may stay open indefinitely, so queue its install check before forwarding.
|
|
42
|
+
// Other commands finish first to avoid racing their own daemon operation.
|
|
43
|
+
if (args[0] === 'rc' && args[1] === 'local' && args[2] === 'bridge') {
|
|
44
|
+
scheduleReconciliation(bin, pkgRoot);
|
|
45
|
+
}
|
|
46
|
+
const r = spawnSync(bin, args, {
|
|
41
47
|
stdio: 'inherit',
|
|
42
48
|
env: { ...process.env, AGIT_INSTALL_CHANNEL: 'npm_global' },
|
|
43
49
|
});
|
|
@@ -45,6 +51,9 @@ function run() {
|
|
|
45
51
|
log.error(`failed to start ${bin}: ${r.error.message}`);
|
|
46
52
|
process.exit(1);
|
|
47
53
|
}
|
|
54
|
+
if (!(args[0] === 'rc' && args[1] === 'local' && args[2] === 'bridge')) {
|
|
55
|
+
scheduleReconciliation(bin, pkgRoot);
|
|
56
|
+
}
|
|
48
57
|
process.exit(r.status === null ? 1 : r.status);
|
|
49
58
|
}
|
|
50
59
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@einsia/agent-git",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.12",
|
|
4
4
|
"description": "Version control for agent sessions — the agit CLI plus skills/hooks/MCP wiring for Claude Code, Codex, OpenCode and Cursor. The AgentGit hub deploys separately.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"agit",
|
|
@@ -49,10 +49,10 @@
|
|
|
49
49
|
"access": "public"
|
|
50
50
|
},
|
|
51
51
|
"optionalDependencies": {
|
|
52
|
-
"@einsia/agent-git-linux-x64": "0.2.
|
|
53
|
-
"@einsia/agent-git-linux-arm64": "0.2.
|
|
54
|
-
"@einsia/agent-git-darwin-x64": "0.2.
|
|
55
|
-
"@einsia/agent-git-darwin-arm64": "0.2.
|
|
56
|
-
"@einsia/agent-git-win32-x64": "0.2.
|
|
52
|
+
"@einsia/agent-git-linux-x64": "0.2.12",
|
|
53
|
+
"@einsia/agent-git-linux-arm64": "0.2.12",
|
|
54
|
+
"@einsia/agent-git-darwin-x64": "0.2.12",
|
|
55
|
+
"@einsia/agent-git-darwin-arm64": "0.2.12",
|
|
56
|
+
"@einsia/agent-git-win32-x64": "0.2.12"
|
|
57
57
|
}
|
|
58
58
|
}
|