@songsid/agend 2.2.0-alpha.1 → 2.2.0-alpha.3
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/README.md +6 -4
- package/README.zh-TW.md +6 -4
- package/dist/agent-cli.js +6 -1
- package/dist/agent-cli.js.map +1 -1
- package/dist/agent-endpoint.d.ts +4 -1
- package/dist/agent-endpoint.js +82 -14
- package/dist/agent-endpoint.js.map +1 -1
- package/dist/agent-token-header.d.ts +13 -0
- package/dist/agent-token-header.js +16 -0
- package/dist/agent-token-header.js.map +1 -0
- package/dist/atomic-write.d.ts +21 -0
- package/dist/atomic-write.js +65 -0
- package/dist/atomic-write.js.map +1 -0
- package/dist/auth-api.js +2 -0
- package/dist/auth-api.js.map +1 -1
- package/dist/backend/antigravity.js +4 -3
- package/dist/backend/antigravity.js.map +1 -1
- package/dist/backend/codex.d.ts +7 -0
- package/dist/backend/codex.js +7 -0
- package/dist/backend/codex.js.map +1 -1
- package/dist/backend/grok.js +1 -1
- package/dist/backend/grok.js.map +1 -1
- package/dist/backend/kiro.d.ts +7 -0
- package/dist/backend/kiro.js +82 -33
- package/dist/backend/kiro.js.map +1 -1
- package/dist/backend/muse.d.ts +7 -0
- package/dist/backend/muse.js +8 -1
- package/dist/backend/muse.js.map +1 -1
- package/dist/backend/types.d.ts +12 -0
- package/dist/backend/types.js.map +1 -1
- package/dist/binary-probe.d.ts +80 -0
- package/dist/binary-probe.js +182 -0
- package/dist/binary-probe.js.map +1 -0
- package/dist/cache-analysis.d.ts +92 -0
- package/dist/cache-analysis.js +147 -0
- package/dist/cache-analysis.js.map +1 -0
- package/dist/cache-ledger.d.ts +108 -0
- package/dist/cache-ledger.js +230 -0
- package/dist/cache-ledger.js.map +1 -0
- package/dist/cache-prices.d.ts +32 -0
- package/dist/cache-prices.js +73 -0
- package/dist/cache-prices.js.map +1 -0
- package/dist/cache-scan.d.ts +39 -0
- package/dist/cache-scan.js +115 -0
- package/dist/cache-scan.js.map +1 -0
- package/dist/cache-service.d.ts +131 -0
- package/dist/cache-service.js +452 -0
- package/dist/cache-service.js.map +1 -0
- package/dist/channel/adapters/discord.d.ts +5 -1
- package/dist/channel/adapters/discord.js +56 -11
- package/dist/channel/adapters/discord.js.map +1 -1
- package/dist/channel/adapters/telegram.d.ts +38 -1
- package/dist/channel/adapters/telegram.js +82 -0
- package/dist/channel/adapters/telegram.js.map +1 -1
- package/dist/channel/mcp-tools.js +1 -1
- package/dist/channel/mcp-tools.js.map +1 -1
- package/dist/channel/tool-router.d.ts +8 -1
- package/dist/channel/tool-router.js +4 -2
- package/dist/channel/tool-router.js.map +1 -1
- package/dist/channel/types.d.ts +21 -0
- package/dist/cli-entry.d.ts +19 -0
- package/dist/cli-entry.js +36 -0
- package/dist/cli-entry.js.map +1 -0
- package/dist/cli.js +376 -54
- package/dist/cli.js.map +1 -1
- package/dist/connection-secrets.d.ts +5 -0
- package/dist/connection-secrets.js.map +1 -1
- package/dist/cpu-profile.js +50 -0
- package/dist/cpu-profile.js.map +1 -1
- package/dist/daemon.d.ts +18 -0
- package/dist/daemon.js +256 -63
- package/dist/daemon.js.map +1 -1
- package/dist/delivery-outbox.d.ts +1 -0
- package/dist/delivery-outbox.js +12 -0
- package/dist/delivery-outbox.js.map +1 -1
- package/dist/discord-permissions.d.ts +33 -0
- package/dist/discord-permissions.js +42 -0
- package/dist/discord-permissions.js.map +1 -0
- package/dist/env-file.d.ts +11 -0
- package/dist/env-file.js +27 -0
- package/dist/env-file.js.map +1 -0
- package/dist/event-log.d.ts +4 -1
- package/dist/event-log.js +6 -3
- package/dist/event-log.js.map +1 -1
- package/dist/fleet-context.d.ts +5 -0
- package/dist/fleet-context.js.map +1 -1
- package/dist/fleet-control-audit.d.ts +5 -0
- package/dist/fleet-control-audit.js +7 -0
- package/dist/fleet-control-audit.js.map +1 -1
- package/dist/fleet-level-config.d.ts +8 -1
- package/dist/fleet-level-config.js +53 -0
- package/dist/fleet-level-config.js.map +1 -1
- package/dist/fleet-manager.d.ts +122 -7
- package/dist/fleet-manager.js +871 -152
- package/dist/fleet-manager.js.map +1 -1
- package/dist/fleet-stop-budget.d.ts +22 -0
- package/dist/fleet-stop-budget.js +95 -0
- package/dist/fleet-stop-budget.js.map +1 -0
- package/dist/full-restart.js +2 -3
- package/dist/full-restart.js.map +1 -1
- package/dist/general-knowledge/skills/persona-emoji/SKILL.md +3 -1
- package/dist/install-lock.d.ts +49 -0
- package/dist/install-lock.js +200 -0
- package/dist/install-lock.js.map +1 -0
- package/dist/instance-config-impact.js +3 -2
- package/dist/instance-config-impact.js.map +1 -1
- package/dist/kiro-transcript-lane.d.ts +8 -4
- package/dist/kiro-transcript-lane.js +66 -44
- package/dist/kiro-transcript-lane.js.map +1 -1
- package/dist/locale.js +96 -0
- package/dist/locale.js.map +1 -1
- package/dist/next-steps.d.ts +5 -0
- package/dist/next-steps.js +26 -0
- package/dist/next-steps.js.map +1 -0
- package/dist/outbound-handlers.js +8 -0
- package/dist/outbound-handlers.js.map +1 -1
- package/dist/outbound-schemas.d.ts +4 -0
- package/dist/outbound-schemas.js +7 -2
- package/dist/outbound-schemas.js.map +1 -1
- package/dist/outbox-open-error.d.ts +7 -0
- package/dist/outbox-open-error.js +15 -0
- package/dist/outbox-open-error.js.map +1 -0
- package/dist/package-preimage.d.ts +26 -0
- package/dist/package-preimage.js +96 -0
- package/dist/package-preimage.js.map +1 -0
- package/dist/pane-context-cache.d.ts +25 -0
- package/dist/pane-context-cache.js +73 -0
- package/dist/pane-context-cache.js.map +1 -0
- package/dist/paths.d.ts +0 -7
- package/dist/paths.js +8 -2
- package/dist/paths.js.map +1 -1
- package/dist/provider-probe.d.ts +15 -0
- package/dist/provider-probe.js +20 -0
- package/dist/provider-probe.js.map +1 -1
- package/dist/public-link-progress.d.ts +73 -0
- package/dist/public-link-progress.js +190 -0
- package/dist/public-link-progress.js.map +1 -0
- package/dist/public-web-gateway.js +2 -2
- package/dist/public-web-gateway.js.map +1 -1
- package/dist/public-web-link.d.ts +6 -1
- package/dist/public-web-link.js +87 -15
- package/dist/public-web-link.js.map +1 -1
- package/dist/quickstart-api.d.ts +93 -20
- package/dist/quickstart-api.js +194 -62
- package/dist/quickstart-api.js.map +1 -1
- package/dist/quickstart.d.ts +15 -0
- package/dist/quickstart.js +110 -33
- package/dist/quickstart.js.map +1 -1
- package/dist/reply-buttons-controller.d.ts +98 -0
- package/dist/reply-buttons-controller.js +174 -0
- package/dist/reply-buttons-controller.js.map +1 -0
- package/dist/reply-buttons.d.ts +113 -0
- package/dist/reply-buttons.js +208 -0
- package/dist/reply-buttons.js.map +1 -0
- package/dist/reply-dedup.js +3 -1
- package/dist/reply-dedup.js.map +1 -1
- package/dist/restart-guard.d.ts +53 -0
- package/dist/restart-guard.js +114 -0
- package/dist/restart-guard.js.map +1 -0
- package/dist/scheduler/db.js +6 -2
- package/dist/scheduler/db.js.map +1 -1
- package/dist/service-activation.d.ts +62 -7
- package/dist/service-activation.js +237 -55
- package/dist/service-activation.js.map +1 -1
- package/dist/service-installer.d.ts +56 -1
- package/dist/service-installer.js +152 -12
- package/dist/service-installer.js.map +1 -1
- package/dist/service-plan.d.ts +65 -0
- package/dist/service-plan.js +108 -0
- package/dist/service-plan.js.map +1 -0
- package/dist/settings-api.js +92 -10
- package/dist/settings-api.js.map +1 -1
- package/dist/settings-authority.d.ts +11 -0
- package/dist/settings-authority.js +128 -0
- package/dist/settings-authority.js.map +1 -0
- package/dist/settings-change.d.ts +5 -0
- package/dist/settings-change.js +37 -8
- package/dist/settings-change.js.map +1 -1
- package/dist/settings-confirmation.d.ts +3 -0
- package/dist/settings-confirmation.js +7 -1
- package/dist/settings-confirmation.js.map +1 -1
- package/dist/settings-effect.js +19 -7
- package/dist/settings-effect.js.map +1 -1
- package/dist/settings-http-confirmation.js +1 -1
- package/dist/settings-http-confirmation.js.map +1 -1
- package/dist/settings-redaction.d.ts +18 -0
- package/dist/settings-redaction.js +58 -0
- package/dist/settings-redaction.js.map +1 -0
- package/dist/setup-form.d.ts +9 -1
- package/dist/setup-form.js +25 -1
- package/dist/setup-form.js.map +1 -1
- package/dist/setup-host.js +15 -6
- package/dist/setup-host.js.map +1 -1
- package/dist/setup-wizard.js +27 -19
- package/dist/setup-wizard.js.map +1 -1
- package/dist/sqlite-open-errors.d.ts +12 -0
- package/dist/sqlite-open-errors.js +13 -0
- package/dist/sqlite-open-errors.js.map +1 -0
- package/dist/systemd-runtime.d.ts +15 -0
- package/dist/systemd-runtime.js +41 -0
- package/dist/systemd-runtime.js.map +1 -0
- package/dist/tmux-control.d.ts +44 -0
- package/dist/tmux-control.js +170 -21
- package/dist/tmux-control.js.map +1 -1
- package/dist/token-env-name.d.ts +37 -0
- package/dist/token-env-name.js +93 -0
- package/dist/token-env-name.js.map +1 -0
- package/dist/tool-permissions.js +5 -3
- package/dist/tool-permissions.js.map +1 -1
- package/dist/topic-commands.d.ts +7 -16
- package/dist/topic-commands.js +56 -85
- package/dist/topic-commands.js.map +1 -1
- package/dist/transcript-monitor.js +3 -0
- package/dist/transcript-monitor.js.map +1 -1
- package/dist/transcript-sources.d.ts +68 -0
- package/dist/transcript-sources.js +336 -29
- package/dist/transcript-sources.js.map +1 -1
- package/dist/transcript-turns.d.ts +39 -0
- package/dist/transcript-turns.js +58 -0
- package/dist/transcript-turns.js.map +1 -0
- package/dist/tunnel/cloudflared-install.d.ts +18 -0
- package/dist/tunnel/cloudflared-install.js +15 -1
- package/dist/tunnel/cloudflared-install.js.map +1 -1
- package/dist/turn-reply-guard.d.ts +3 -0
- package/dist/turn-reply-guard.js.map +1 -1
- package/dist/ui/chat-commands.js +43 -0
- package/dist/ui/chat-render.js +34 -8
- package/dist/ui/chat-store.js +51 -2
- package/dist/ui/chat-strings.js +30 -2
- package/dist/ui/chat-thread.js +141 -47
- package/dist/ui/first-run.js +64 -0
- package/dist/ui/fleet-cache.js +168 -0
- package/dist/ui/html-attachment.js +26 -0
- package/dist/ui/panel-chat.js +117 -23
- package/dist/ui/panel-fleet.js +31 -17
- package/dist/ui/panel-settings.js +29 -15
- package/dist/ui/preview-panel.js +237 -0
- package/dist/ui/preview.js +7 -3
- package/dist/ui/settings-apply.js +3 -1
- package/dist/ui/settings-dialogs.js +95 -25
- package/dist/ui/settings-discord.js +130 -0
- package/dist/ui/settings-model.js +21 -0
- package/dist/ui/settings-strings.js +50 -8
- package/dist/ui/settings-token.js +54 -0
- package/dist/ui/settings-wizard.js +63 -29
- package/dist/ui/shared/app-route.js +1 -1
- package/dist/ui/shared/app-shell.js +25 -37
- package/dist/ui/shared/app-store.js +5 -3
- package/dist/ui/shared/app-stream.js +2 -1
- package/dist/ui/shared/app.css +181 -5
- package/dist/ui/shared/app.js +2 -1
- package/dist/ui/shared/instance-nav.js +310 -0
- package/dist/ui/shared/panel-view.js +12 -190
- package/dist/ui/shared/signin.js +13 -1
- package/dist/ui/shared/ui-icons.js +2 -0
- package/dist/ui/shared/view-roster-store.js +5 -0
- package/dist/ui/shared/view-strings.js +12 -0
- package/dist/ui/signin.html +2 -1
- package/dist/update-dispatch.d.ts +28 -0
- package/dist/update-dispatch.js +73 -0
- package/dist/update-dispatch.js.map +1 -0
- package/dist/update-install.d.ts +78 -8
- package/dist/update-install.js +158 -30
- package/dist/update-install.js.map +1 -1
- package/dist/update-launch.d.ts +22 -0
- package/dist/update-launch.js +106 -0
- package/dist/update-launch.js.map +1 -0
- package/dist/update-marker.js +3 -8
- package/dist/update-marker.js.map +1 -1
- package/dist/usage/format-rich.js +7 -1
- package/dist/usage/format-rich.js.map +1 -1
- package/dist/usage/i18n-keys.d.ts +1 -1
- package/dist/usage/i18n-keys.js +1 -1
- package/dist/usage/i18n-keys.js.map +1 -1
- package/dist/usage/providers.d.ts +4 -0
- package/dist/usage/providers.js +2 -1
- package/dist/usage/providers.js.map +1 -1
- package/dist/usage/usage-api.js +48 -2
- package/dist/usage/usage-api.js.map +1 -1
- package/dist/view-api.d.ts +40 -0
- package/dist/view-api.js +28 -10
- package/dist/view-api.js.map +1 -1
- package/dist/web-api.d.ts +22 -0
- package/dist/web-api.js +115 -16
- package/dist/web-api.js.map +1 -1
- package/dist/web-auth.d.ts +8 -0
- package/dist/web-auth.js +34 -8
- package/dist/web-auth.js.map +1 -1
- package/dist/web-chat-history.d.ts +21 -0
- package/dist/web-chat-history.js +44 -0
- package/dist/web-chat-history.js.map +1 -1
- package/dist/web-commands.d.ts +77 -0
- package/dist/web-commands.js +81 -0
- package/dist/web-commands.js.map +1 -0
- package/dist/web-host-guard.d.ts +18 -2
- package/dist/web-host-guard.js +30 -5
- package/dist/web-host-guard.js.map +1 -1
- package/dist/web-session.d.ts +7 -1
- package/dist/web-session.js +23 -2
- package/dist/web-session.js.map +1 -1
- package/dist/web-shell-routes.d.ts +1 -1
- package/dist/web-shell-routes.js +1 -1
- package/dist/web-shell-routes.js.map +1 -1
- package/launcher/agend +110 -0
- package/launcher/agend-agent +110 -0
- package/launcher/agend-agent.cjs +4 -0
- package/launcher/agend.cjs +4 -0
- package/launcher/install-admission.cjs +105 -0
- package/launcher/launch.cjs +64 -0
- package/launcher/postinstall.cjs +118 -0
- package/launcher/run-in-process.cjs +13 -0
- package/launcher/runtime-platform.cjs +86 -0
- package/launcher/runtime-select.cjs +217 -0
- package/package.json +12 -4
- package/scripts/preinstall-guard.cjs +40 -21
- package/templates/launchd.plist.ejs +2 -2
- package/templates/systemd.service.ejs +4 -4
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
// #1450: npm's `postinstall` for @songsid/agend. It runs under whatever Node runs npm (possibly an old one) and
|
|
4
|
+
// decides, inside the install transaction, whether this install can run at all — refusing (exit 1) makes npm roll the
|
|
5
|
+
// install back (proven by the npm-rollback-proof gate, #1455):
|
|
6
|
+
// - this release's bundled Node is present (supported host): prove it — exact version, N-API ≥ 10, platform/arch,
|
|
7
|
+
// and a better-sqlite3 database opened in the main thread AND in a worker — then write the receipt the launcher
|
|
8
|
+
// checks on every start;
|
|
9
|
+
// - no bundled Node (unsupported host, none pinned yet, optional dependencies omitted): the Node running npm must
|
|
10
|
+
// qualify itself.
|
|
11
|
+
var fs = require("fs");
|
|
12
|
+
var path = require("path");
|
|
13
|
+
var crypto = require("crypto");
|
|
14
|
+
var childProcess = require("child_process");
|
|
15
|
+
var platform = require("./runtime-platform.cjs");
|
|
16
|
+
var select = require("./runtime-select.cjs");
|
|
17
|
+
var admission = require("./install-admission.cjs");
|
|
18
|
+
|
|
19
|
+
/** Runs on the candidate Node with argv[1] = the package dir: version, N-API, and a real DB open in both threads. */
|
|
20
|
+
var PROOF = [
|
|
21
|
+
"const { createRequire } = require('node:module');",
|
|
22
|
+
"const { Worker } = require('node:worker_threads');",
|
|
23
|
+
"const dir = process.argv[1];",
|
|
24
|
+
"const open = () => { const D = createRequire(require('node:path').join(dir, 'package.json'))('better-sqlite3'); const db = new D(':memory:'); const one = db.prepare('select 1 as one').get().one; db.close(); return one; };",
|
|
25
|
+
"if (open() !== 1) process.exit(3);",
|
|
26
|
+
"const w = new Worker(`const { createRequire } = require('node:module'); const D = createRequire(require('node:path').join(${JSON.stringify(dir)}, 'package.json'))('better-sqlite3'); const db = new D(':memory:'); const one = db.prepare('select 1 as one').get().one; db.close(); require('node:worker_threads').parentPort.postMessage(one);`, { eval: true });",
|
|
27
|
+
// The worker's answer AND its clean exit (the database closed inside it): any other end fails the proof.
|
|
28
|
+
"let answer = null;",
|
|
29
|
+
"w.on('message', one => { answer = one; });",
|
|
30
|
+
"w.on('error', () => process.exit(5));",
|
|
31
|
+
"w.on('exit', code => { if (code !== 0 || answer !== 1) process.exit(4); process.stdout.write(JSON.stringify({ node: process.versions.node, napi: Number(process.versions.napi), platform: process.platform, arch: process.arch })); process.exit(0); });",
|
|
32
|
+
].join("\n");
|
|
33
|
+
|
|
34
|
+
function refuse(reason, recovery) {
|
|
35
|
+
process.stderr.write("\n AgEnD cannot be installed here: " + reason + ".\n npm is rolling this install back; the previous install, if any, stays.\n " + recovery + "\n\n");
|
|
36
|
+
process.exit(1);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function sha256(file) {
|
|
40
|
+
return crypto.createHash("sha256").update(fs.readFileSync(file)).digest("hex");
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** #1519 P7: the last line of a successful install (npm shows it with --foreground-scripts; install.sh says the same). */
|
|
44
|
+
var NEXT_STEP = " Next: run `agend quickstart` to set up AgEnD.";
|
|
45
|
+
|
|
46
|
+
function main(deps) {
|
|
47
|
+
var d = deps || {};
|
|
48
|
+
var pkg = select.packageDir(d.launcherDir || __dirname);
|
|
49
|
+
if (!pkg) refuse("the installed package is not @songsid/agend", "npm install -g @songsid/agend");
|
|
50
|
+
// A receipt describes THIS install's verification and nothing else: whatever is there now (one npm left behind, one
|
|
51
|
+
// copied in) goes first, so selection and installation see the same state (runtime-select.cjs).
|
|
52
|
+
[select.KEY, select.RECEIPT].forEach(function (name) {
|
|
53
|
+
var file = path.join(pkg.dir, name);
|
|
54
|
+
if (select.absent(file)) return;
|
|
55
|
+
try { fs.unlinkSync(file); } catch (e) { /* checked below */ }
|
|
56
|
+
if (!select.absent(file)) refuse("an old runtime receipt at " + file + " cannot be removed", "remove it, then: npm install -g @songsid/agend@" + pkg.manifest.version);
|
|
57
|
+
});
|
|
58
|
+
// C1: an `agend update` owns this prefix right now → only its own npm child may install (install-admission.cjs).
|
|
59
|
+
var admitted = admission.admit(pkg.dir, { env: d.env, processStart: d.processStart });
|
|
60
|
+
if (!admitted.ok) refuse(admitted.reason, "Never run two installs of @songsid/agend into one prefix at once, or one while `agend update` runs.");
|
|
61
|
+
var host = d.host || platform.hostPlatform();
|
|
62
|
+
var running = d.versions || { node: process.versions.node, napi: Number(process.versions.napi) };
|
|
63
|
+
var engines = (pkg.manifest.engines && pkg.manifest.engines.node) || ">=22.14.0";
|
|
64
|
+
var pin = select.pinnedRuntime(pkg.manifest, host);
|
|
65
|
+
var support = platform.runtimeSupport(host);
|
|
66
|
+
var log = d.log || function (line) { process.stdout.write(line + "\n"); };
|
|
67
|
+
var installed = function () { log(NEXT_STEP); return 0; };
|
|
68
|
+
|
|
69
|
+
if (pin && support.supported) {
|
|
70
|
+
var candidate = select.runtimeCandidate(pkg.dir, pin);
|
|
71
|
+
if (candidate.exists) {
|
|
72
|
+
if (!candidate.manifest || candidate.manifest.name !== pin.name || candidate.manifest.version !== pin.version) {
|
|
73
|
+
refuse("the bundled Node package is not " + pin.name + "@" + pin.version, "npm install -g @songsid/agend@" + pkg.manifest.version);
|
|
74
|
+
}
|
|
75
|
+
var st;
|
|
76
|
+
try { st = fs.lstatSync(candidate.node); } catch (e) { st = null; }
|
|
77
|
+
if (!st || !st.isFile()) refuse("the bundled Node binary is missing or not a regular file", "npm install -g @songsid/agend@" + pkg.manifest.version);
|
|
78
|
+
var r = childProcess.spawnSync(candidate.node, ["-e", PROOF, pkg.dir], { encoding: "utf8", timeout: 30000, stdio: ["ignore", "pipe", "pipe"] });
|
|
79
|
+
var seen = null;
|
|
80
|
+
try { seen = r.status === 0 ? JSON.parse(String(r.stdout).trim()) : null; } catch (e) { seen = null; }
|
|
81
|
+
var wantNode = pin.version.replace(/-agend\.\d+$/, "");
|
|
82
|
+
if (!seen || seen.node !== wantNode || !(seen.napi >= select.MIN_NAPI) || seen.platform !== host.platform || seen.arch !== host.arch) {
|
|
83
|
+
refuse("the bundled Node did not pass its check (" + (seen ? "Node " + seen.node + ", N-API " + seen.napi + ", " + seen.platform + "-" + seen.arch : "exit " + (r.status === null ? r.signal : r.status) + ": " + String(r.stderr || "").trim().split("\n").pop()) + ")", "npm install -g @songsid/agend@" + pkg.manifest.version);
|
|
84
|
+
}
|
|
85
|
+
var real = fs.realpathSync(candidate.node);
|
|
86
|
+
// The receipt contract (runtime-select.cjs) keeps every string plain, so a shell can check it exactly too.
|
|
87
|
+
if (!select.PLAIN.test(real)) refuse("AgEnD's install path " + JSON.stringify(real) + " contains a quote, backslash or control character", "install it under a plain path");
|
|
88
|
+
var finalStat = fs.statSync(real);
|
|
89
|
+
var receipt = {
|
|
90
|
+
receipt: 2, pinnedVersion: pin.version, nodePath: real, size: finalStat.size, mtime: Math.floor(finalStat.mtimeMs / 1000), sha256: sha256(real),
|
|
91
|
+
napi: seen.napi, platform: host.platform, arch: host.arch, libc: host.glibc ? "glibc" : null, verifiedAt: new Date().toISOString(),
|
|
92
|
+
};
|
|
93
|
+
var tmp = path.join(pkg.dir, select.RECEIPT + "." + process.pid + ".tmp");
|
|
94
|
+
fs.writeFileSync(tmp, select.receiptText(receipt));
|
|
95
|
+
fs.renameSync(tmp, path.join(pkg.dir, select.RECEIPT));
|
|
96
|
+
// The admission key goes LAST: it binds the receipt just written (runtime-select.cjs).
|
|
97
|
+
var key = select.runtimeKey(pkg.dir, candidate, host);
|
|
98
|
+
if (key === null) refuse("the bundled Node's admission key cannot be computed", "npm install -g @songsid/agend@" + pkg.manifest.version);
|
|
99
|
+
var keyTmp = path.join(pkg.dir, select.KEY + "." + process.pid + ".tmp");
|
|
100
|
+
fs.writeFileSync(keyTmp, key);
|
|
101
|
+
fs.renameSync(keyTmp, path.join(pkg.dir, select.KEY));
|
|
102
|
+
log(" ✓ AgEnD will run on its bundled Node " + seen.node + " (verified: N-API " + seen.napi + ", a database opened in the main thread and a worker)");
|
|
103
|
+
return installed();
|
|
104
|
+
}
|
|
105
|
+
if (select.qualifies(running, engines)) {
|
|
106
|
+
log(" ⚠ AgEnD's bundled Node was not installed (--omit=optional?); it will run on this Node " + running.node);
|
|
107
|
+
return installed();
|
|
108
|
+
}
|
|
109
|
+
refuse("its bundled Node (" + pin.name + ") was not installed, and this Node " + running.node + " is older than AgEnD needs (" + engines + ")",
|
|
110
|
+
"If optional dependencies were omitted or a registry mirror blocks " + pin.name + ", allow it and retry; or install Node " + engines + " first.");
|
|
111
|
+
}
|
|
112
|
+
if (select.qualifies(running, engines)) return installed();
|
|
113
|
+
refuse((pin ? support.reason : "this release bundles no Node") + ", and this Node " + running.node + " is older than AgEnD needs (" + engines + ")",
|
|
114
|
+
"Install Node " + engines + ", then: npm install -g @songsid/agend@" + pkg.manifest.version);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
module.exports = { main: main, PROOF: PROOF, NEXT_STEP: NEXT_STEP };
|
|
118
|
+
if (require.main === module) process.exit(main() || 0);
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Loaded only when the running Node is the selected one — a Node new enough to parse `import()`; older Nodes never
|
|
3
|
+
// load this file, so their parse of the launcher stays clean. process.argv[1] becomes the inner entry, as when the
|
|
4
|
+
// CLI is run directly (service install, completion and restart read it).
|
|
5
|
+
var url = require("url");
|
|
6
|
+
|
|
7
|
+
module.exports = function runInProcess(entry) {
|
|
8
|
+
process.argv[1] = entry;
|
|
9
|
+
return import(url.pathToFileURL(entry).href).catch(function (err) {
|
|
10
|
+
process.stderr.write(String((err && err.stack) || err) + "\n");
|
|
11
|
+
process.exit(1);
|
|
12
|
+
});
|
|
13
|
+
};
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// #1450 C3: the one answer to "can this host run AgEnD's bundled Node?", shared by preinstall, postinstall and the
|
|
3
|
+
// launcher. CommonJS that very old Nodes still parse (var, function, no optional chaining): it may run on the host's
|
|
4
|
+
// own old Node, before anything decides which Node AgEnD itself will use.
|
|
5
|
+
var os = require("os");
|
|
6
|
+
|
|
7
|
+
/** The runtime packages AgEnD ships: official glibc Linux builds and macOS builds (no musl, #1450). */
|
|
8
|
+
var SHIPPED = ["linux-x64", "linux-arm64", "darwin-x64", "darwin-arm64"];
|
|
9
|
+
/** The official Node 22 builds need glibc ≥ 2.28 on Linux and macOS 11 (Darwin kernel 20) or newer. */
|
|
10
|
+
var MIN_GLIBC = [2, 28];
|
|
11
|
+
var MIN_DARWIN_KERNEL = 20;
|
|
12
|
+
|
|
13
|
+
function numbers(text) {
|
|
14
|
+
return String(text).split(".").map(function (part) { return parseInt(part, 10); });
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function atLeast(have, want) {
|
|
18
|
+
for (var i = 0; i < want.length; i++) {
|
|
19
|
+
var h = have[i] || 0;
|
|
20
|
+
if (h > want[i]) return true;
|
|
21
|
+
if (h < want[i]) return false;
|
|
22
|
+
}
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** glibc's version when this Node runs on glibc; null on musl or when it cannot be told. */
|
|
27
|
+
function glibcVersion(report) {
|
|
28
|
+
try {
|
|
29
|
+
var r = report || (process.report && process.report.getReport && process.report.getReport());
|
|
30
|
+
var v = r && r.header && r.header.glibcVersionRuntime;
|
|
31
|
+
return typeof v === "string" && v ? v : null;
|
|
32
|
+
} catch (e) {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* The host as the runtime packages see it. `probe` is injectable for tests:
|
|
39
|
+
* { platform, arch, glibc (string|null), darwinRelease (string|null) }.
|
|
40
|
+
*/
|
|
41
|
+
function hostPlatform(probe) {
|
|
42
|
+
var p = probe || {};
|
|
43
|
+
var platform = p.platform || process.platform;
|
|
44
|
+
var arch = p.arch || process.arch;
|
|
45
|
+
return {
|
|
46
|
+
platform: platform,
|
|
47
|
+
arch: arch,
|
|
48
|
+
id: platform + "-" + arch,
|
|
49
|
+
glibc: "glibc" in p ? p.glibc : (platform === "linux" ? glibcVersion() : null),
|
|
50
|
+
darwinRelease: "darwinRelease" in p ? p.darwinRelease : (platform === "darwin" ? os.release() : null),
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Whether a bundled runtime can run here, and why not. */
|
|
55
|
+
function runtimeSupport(host) {
|
|
56
|
+
if (SHIPPED.indexOf(host.id) < 0) return { supported: false, reason: "no AgEnD runtime is published for " + host.id };
|
|
57
|
+
if (host.platform === "linux") {
|
|
58
|
+
if (!host.glibc) return { supported: false, reason: "this Linux is not glibc-based (musl?), and the bundled Node needs glibc" };
|
|
59
|
+
if (!atLeast(numbers(host.glibc), MIN_GLIBC)) return { supported: false, reason: "glibc " + host.glibc + " is older than the 2.28 the bundled Node needs" };
|
|
60
|
+
}
|
|
61
|
+
if (host.platform === "darwin") {
|
|
62
|
+
var kernel = numbers(host.darwinRelease || "0")[0];
|
|
63
|
+
if (!(kernel >= MIN_DARWIN_KERNEL)) return { supported: false, reason: "macOS older than 11 (Darwin " + host.darwinRelease + ") cannot run the bundled Node" };
|
|
64
|
+
}
|
|
65
|
+
return { supported: true, reason: null };
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Does a Node version satisfy an `engines.node` range? Only the forms AgEnD uses: alternatives joined by `||`, each
|
|
70
|
+
* `^x.y.z` (same major, at least x.y.z) or `>=x[.y[.z]]`. The version must be a full stable `x.y.z`: as in npm's
|
|
71
|
+
* semver, a prerelease (`24.0.0-nightly…`, `22.14.0-rc.1`) satisfies no stable alternative.
|
|
72
|
+
*/
|
|
73
|
+
function satisfiesEngines(version, range) {
|
|
74
|
+
var full = /^v?(\d+)\.(\d+)\.(\d+)$/.exec(String(version));
|
|
75
|
+
if (!full) return false;
|
|
76
|
+
var v = [+full[1], +full[2], +full[3]];
|
|
77
|
+
return String(range).split("||").some(function (alt) {
|
|
78
|
+
var a = alt.trim();
|
|
79
|
+
var m;
|
|
80
|
+
if ((m = /^\^(\d+)\.(\d+)\.(\d+)$/.exec(a))) return v[0] === +m[1] && atLeast(v, [+m[1], +m[2], +m[3]]);
|
|
81
|
+
if ((m = /^>=\s*(\d+)(?:\.(\d+))?(?:\.(\d+))?$/.exec(a))) return atLeast(v, [+m[1], +(m[2] || 0), +(m[3] || 0)]);
|
|
82
|
+
return false;
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
module.exports = { SHIPPED: SHIPPED, hostPlatform: hostPlatform, runtimeSupport: runtimeSupport, satisfiesEngines: satisfiesEngines, glibcVersion: glibcVersion };
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// #1450 C2: one verified interpreter-selection protocol, shared by the launchers, the postinstall and the updater's
|
|
3
|
+
// probe of a target package. Same syntax constraints as runtime-platform.cjs. It never touches the network and does
|
|
4
|
+
// at most one bounded probe.
|
|
5
|
+
var fs = require("fs");
|
|
6
|
+
var path = require("path");
|
|
7
|
+
var childProcess = require("child_process");
|
|
8
|
+
var platform = require("./runtime-platform.cjs");
|
|
9
|
+
|
|
10
|
+
var RECEIPT = ".agend-runtime.json";
|
|
11
|
+
var KEY = ".agend-runtime.key";
|
|
12
|
+
var MIN_NAPI = 10;
|
|
13
|
+
|
|
14
|
+
function readJson(file) {
|
|
15
|
+
try { return JSON.parse(fs.readFileSync(file, "utf8")); } catch (e) { return null; }
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** Physically absent (lstat: ENOENT/ENOTDIR)? A dangling symlink, or a path that cannot be inspected, is PRESENT. */
|
|
19
|
+
function absent(file) {
|
|
20
|
+
try { fs.lstatSync(file); return false; } catch (e) { return e.code === "ENOENT" || e.code === "ENOTDIR"; }
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* THE RECEIPT CONTRACT (#1460 review), shared word for word with the sh bins (launcher/agend), which check it before
|
|
25
|
+
* they exec anything: the receipt is exactly `JSON.stringify(receipt, null, 2) + "\n"` of these keys in this order —
|
|
26
|
+
* strings plain (no `"`, `\` or control characters), numbers non-negative integers, `libc` "glibc" or null. Anything
|
|
27
|
+
* else — unreadable, not JSON, valid JSON in another shape or layout — is "invalid". `mtime` is whole seconds (what
|
|
28
|
+
* `stat` gives a shell, and what survives a copy that preserves timestamps).
|
|
29
|
+
*/
|
|
30
|
+
var RECEIPT_KEYS = ["receipt", "pinnedVersion", "nodePath", "size", "mtime", "sha256", "napi", "platform", "arch", "libc", "verifiedAt"];
|
|
31
|
+
var PLAIN = /^[^"\\\u0000-\u001f]*$/;
|
|
32
|
+
function receiptText(r) {
|
|
33
|
+
var ordered = {};
|
|
34
|
+
RECEIPT_KEYS.forEach(function (k) { ordered[k] = r[k]; });
|
|
35
|
+
return JSON.stringify(ordered, null, 2) + "\n";
|
|
36
|
+
}
|
|
37
|
+
function receiptShapeOk(r) {
|
|
38
|
+
var int = function (n) { return typeof n === "number" && Number.isSafeInteger(n) && n >= 0; };
|
|
39
|
+
var str = function (s) { return typeof s === "string" && PLAIN.test(s); };
|
|
40
|
+
return r.receipt === 2 && str(r.pinnedVersion) && /^\d+\.\d+\.\d+(-agend\.\d+)?$/.test(r.pinnedVersion)
|
|
41
|
+
&& str(r.nodePath) && r.nodePath.charAt(0) === "/" && int(r.size) && int(r.mtime) && typeof r.sha256 === "string" && /^[0-9a-f]{64}$/.test(r.sha256)
|
|
42
|
+
&& int(r.napi) && str(r.platform) && r.platform !== "" && str(r.arch) && r.arch !== "" && (r.libc === "glibc" || r.libc === null) && str(r.verifiedAt) && r.verifiedAt !== "";
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** The receipt, by state: "absent" only when no file is there; "valid" only in the contract's exact form; else "invalid". */
|
|
46
|
+
function readReceipt(file) {
|
|
47
|
+
if (absent(file)) return { state: "absent" };
|
|
48
|
+
var text, value;
|
|
49
|
+
try { text = fs.readFileSync(file, "utf8"); value = JSON.parse(text); } catch (e) { return { state: "invalid" }; }
|
|
50
|
+
if (!value || typeof value !== "object" || Array.isArray(value) || Object.keys(value).join(",") !== RECEIPT_KEYS.join(",")) return { state: "invalid" };
|
|
51
|
+
return receiptShapeOk(value) && text === receiptText(value) ? { state: "valid", value: value } : { state: "invalid" };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function realpath(file) {
|
|
55
|
+
try { return fs.realpathSync(file); } catch (e) { return null; }
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** The package that contains this launcher: realpath(launcher/..), and it must be @songsid/agend. */
|
|
59
|
+
function packageDir(launcherDir) {
|
|
60
|
+
var dir = realpath(path.join(launcherDir, ".."));
|
|
61
|
+
var manifest = dir && readJson(path.join(dir, "package.json"));
|
|
62
|
+
return manifest && manifest.name === "@songsid/agend" ? { dir: dir, manifest: manifest } : null;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** This release's runtime package name and exact pinned version, or null when none is pinned yet. */
|
|
66
|
+
function pinnedRuntime(manifest, host) {
|
|
67
|
+
var name = "@songsid/agend-node-" + host.id;
|
|
68
|
+
var version = manifest.optionalDependencies && manifest.optionalDependencies[name];
|
|
69
|
+
return typeof version === "string" && /^\d+\.\d+\.\d+(-agend\.\d+)?$/.test(version) ? { name: name, version: version } : null;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** The candidate is exactly <pkgDir>/node_modules/<name> — never require.resolve, which can walk up to an ancestor. */
|
|
73
|
+
function runtimeCandidate(pkgDir, pin) {
|
|
74
|
+
var dir = path.join(pkgDir, "node_modules", pin.name);
|
|
75
|
+
var exists = !absent(dir);
|
|
76
|
+
var manifest = exists ? readJson(path.join(dir, "package.json")) : null;
|
|
77
|
+
return { dir: dir, exists: exists, manifest: manifest, node: path.join(dir, "bin", "node") };
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** Run `node` once, bounded, to read its version and N-API level. */
|
|
81
|
+
function probeNode(node, timeoutMs) {
|
|
82
|
+
try {
|
|
83
|
+
var r = childProcess.spawnSync(node, ["-p", "JSON.stringify({ node: process.versions.node, napi: Number(process.versions.napi) })"], { encoding: "utf8", timeout: timeoutMs || 1000, stdio: ["ignore", "pipe", "ignore"] });
|
|
84
|
+
if (r.status !== 0) return null;
|
|
85
|
+
var v = JSON.parse(String(r.stdout).trim());
|
|
86
|
+
return typeof v.node === "string" && typeof v.napi === "number" ? v : null;
|
|
87
|
+
} catch (e) {
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function qualifies(versions, engines) {
|
|
93
|
+
return versions && platform.satisfiesEngines(versions.node, engines) && Number(versions.napi) >= MIN_NAPI;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** POSIX `cksum`: CRC-32 (0x04C11DB7, MSB first), then the byte length (LSB first), complemented; "<crc> <length>". */
|
|
97
|
+
var CRC_TABLE = (function () {
|
|
98
|
+
var t = [];
|
|
99
|
+
for (var i = 0; i < 256; i++) { var c = i << 24; for (var j = 0; j < 8; j++) c = c & 0x80000000 ? (c << 1) ^ 0x04C11DB7 : c << 1; t.push(c >>> 0); }
|
|
100
|
+
return t;
|
|
101
|
+
})();
|
|
102
|
+
function cksum(buf) {
|
|
103
|
+
var crc = 0;
|
|
104
|
+
var step = function (b) { crc = ((crc << 8) ^ CRC_TABLE[((crc >>> 24) ^ b) & 0xff]) >>> 0; };
|
|
105
|
+
for (var i = 0; i < buf.length; i++) step(buf[i]);
|
|
106
|
+
for (var n = buf.length; n > 0; n = Math.floor(n / 256)) step(n & 0xff);
|
|
107
|
+
return ((~crc) >>> 0) + " " + buf.length;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* THE ADMISSION KEY (#1460 r3), the one thing the sh bins check before they exec the bundled Node — byte for byte, by
|
|
112
|
+
* rebuilding it from their own measurements (launcher/agend) — and that this selection requires as well. One fact per
|
|
113
|
+
* line: the host's os and cpu, the Node's realpath, size and mtime (whole seconds), and the POSIX `cksum` of both
|
|
114
|
+
* manifests and of the receipt. NO host VERSION (glibc, Darwin kernel): an OS update must never lock AgEnD out of
|
|
115
|
+
* the Node it verified (#1450 leader review) — whether the host can still run it is runtimeSupport()'s minimums, judged
|
|
116
|
+
* fresh on every start, and the libc family is fixed by the package's own `libc` field at install. The postinstall writes it LAST, after everything it binds is
|
|
117
|
+
* final and verified; any later change to a manifest or the receipt changes its CRC, any change to the binary its
|
|
118
|
+
* size or mtime — and then neither side runs it. null when a bound file cannot be read.
|
|
119
|
+
*/
|
|
120
|
+
function runtimeKey(pkgDir, candidate, host) {
|
|
121
|
+
try {
|
|
122
|
+
// Bytes, never decoded text: a path is whatever bytes the filesystem has (#1460 r4), exactly as the shell prints it.
|
|
123
|
+
var real = fs.realpathSync(candidate.node, { encoding: "buffer" });
|
|
124
|
+
var st = fs.lstatSync(real);
|
|
125
|
+
if (!st.isFile()) return null;
|
|
126
|
+
var line = function (text) { return Buffer.from(text + "\n", "utf8"); };
|
|
127
|
+
return Buffer.concat([
|
|
128
|
+
line("agend-runtime-key 2"),
|
|
129
|
+
line("os " + host.platform),
|
|
130
|
+
line("cpu " + host.arch),
|
|
131
|
+
Buffer.from("node ", "utf8"), real, Buffer.from("\n", "utf8"),
|
|
132
|
+
line("size " + st.size),
|
|
133
|
+
line("mtime " + Math.floor(st.mtimeMs / 1000)),
|
|
134
|
+
line("package " + cksum(fs.readFileSync(path.join(pkgDir, "package.json")))),
|
|
135
|
+
line("runtime " + cksum(fs.readFileSync(path.join(candidate.dir, "package.json")))),
|
|
136
|
+
line("receipt " + cksum(fs.readFileSync(path.join(pkgDir, RECEIPT)))),
|
|
137
|
+
]);
|
|
138
|
+
} catch (e) {
|
|
139
|
+
return null;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
function keyMatches(pkgDir, candidate, host) {
|
|
143
|
+
var want = runtimeKey(pkgDir, candidate, host);
|
|
144
|
+
var have;
|
|
145
|
+
try { have = fs.readFileSync(path.join(pkgDir, KEY)); } catch (e) { return false; }
|
|
146
|
+
return want !== null && Buffer.compare(have, want) === 0;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/** Does the receipt the postinstall wrote still describe this exact binary? (cheap: no hashing at run time) */
|
|
150
|
+
function receiptMatches(receipt, candidate, pin) {
|
|
151
|
+
if (!receipt || receipt.pinnedVersion !== pin.version) return false;
|
|
152
|
+
var real = realpath(candidate.node);
|
|
153
|
+
if (!real || real !== receipt.nodePath) return false;
|
|
154
|
+
try {
|
|
155
|
+
var st = fs.lstatSync(real);
|
|
156
|
+
return st.isFile() && st.size === receipt.size && Math.floor(st.mtimeMs / 1000) === receipt.mtime;
|
|
157
|
+
} catch (e) {
|
|
158
|
+
return false;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Choose the Node that runs AgEnD. Result:
|
|
164
|
+
* { ok: true, node, source: "override"|"runtime"|"system", pkgDir, warning? }
|
|
165
|
+
* { ok: false, reason, recovery }
|
|
166
|
+
* deps (injectable for tests): env, execPath, versions ({node, napi}), host (hostPlatform() result), probe(node).
|
|
167
|
+
*/
|
|
168
|
+
function selectRuntime(launcherDir, deps) {
|
|
169
|
+
var d = deps || {};
|
|
170
|
+
var env = d.env || process.env;
|
|
171
|
+
var execPath = d.execPath || process.execPath;
|
|
172
|
+
var running = d.versions || { node: process.versions.node, napi: Number(process.versions.napi) };
|
|
173
|
+
var host = d.host || platform.hostPlatform();
|
|
174
|
+
var probe = d.probe || probeNode;
|
|
175
|
+
var pkg = packageDir(launcherDir);
|
|
176
|
+
if (!pkg) return { ok: false, reason: "this launcher is not inside an @songsid/agend package", recovery: "npm install -g @songsid/agend" };
|
|
177
|
+
var engines = (pkg.manifest.engines && pkg.manifest.engines.node) || ">=22.14.0";
|
|
178
|
+
var recovery = "npm install -g @songsid/agend@" + pkg.manifest.version;
|
|
179
|
+
|
|
180
|
+
// 1. An explicit override: absolute, executable, and passes the probe — or a clear failure, never a fallback.
|
|
181
|
+
if (env.AGEND_NODE) {
|
|
182
|
+
var override = env.AGEND_NODE;
|
|
183
|
+
if (!path.isAbsolute(override)) return { ok: false, reason: "AGEND_NODE must be an absolute path (got " + JSON.stringify(override) + ")", recovery: "unset AGEND_NODE, or point it at a Node " + engines };
|
|
184
|
+
try { fs.accessSync(override, fs.constants.X_OK); } catch (e) { return { ok: false, reason: "AGEND_NODE (" + override + ") is not an executable file", recovery: "unset AGEND_NODE, or point it at a Node " + engines }; }
|
|
185
|
+
var seen = probe(override, 1000);
|
|
186
|
+
if (!qualifies(seen, engines)) return { ok: false, reason: "AGEND_NODE (" + override + ") is Node " + (seen ? seen.node + " with N-API " + seen.napi : "that did not answer") + "; AgEnD needs " + engines + " with N-API " + MIN_NAPI, recovery: "unset AGEND_NODE, or point it at a Node " + engines };
|
|
187
|
+
return { ok: true, node: realpath(override) || override, source: "override", pkgDir: pkg.dir };
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
var pin = pinnedRuntime(pkg.manifest, host);
|
|
191
|
+
var support = platform.runtimeSupport(host);
|
|
192
|
+
if (pin && support.supported) {
|
|
193
|
+
var candidate = runtimeCandidate(pkg.dir, pin);
|
|
194
|
+
var receipt = readReceipt(path.join(pkg.dir, RECEIPT));
|
|
195
|
+
// 2. The verified runtime of this release — and its admission key, exactly what the sh bins check (see above).
|
|
196
|
+
if (candidate.exists && candidate.manifest && candidate.manifest.name === pin.name && candidate.manifest.version === pin.version && receipt.state === "valid" && receiptMatches(receipt.value, candidate, pin) && keyMatches(pkg.dir, candidate, host)) {
|
|
197
|
+
return { ok: true, node: realpath(candidate.node), source: "runtime", pkgDir: pkg.dir }; // the binary checked, never a path the receipt names
|
|
198
|
+
}
|
|
199
|
+
// 3. Supported platform, runtime missing/partial/corrupt: refuse — never a silent fallback. The one exception is
|
|
200
|
+
// an install where the runtime was skipped altogether — PHYSICALLY nothing there, no directory (not even a
|
|
201
|
+
// dangling link) and no receipt file (--ignore-scripts or --omit=optional) — which may use a qualifying system
|
|
202
|
+
// Node, with a warning.
|
|
203
|
+
if (candidate.exists || receipt.state !== "absent" || !absent(path.join(pkg.dir, KEY))) {
|
|
204
|
+
return { ok: false, reason: "the bundled Node (" + pin.name + "@" + pin.version + ") is missing, incomplete or changed since it was verified", recovery: recovery };
|
|
205
|
+
}
|
|
206
|
+
if (qualifies(running, engines)) {
|
|
207
|
+
return { ok: true, node: realpath(execPath) || execPath, source: "system", pkgDir: pkg.dir, warning: "the bundled Node was not installed (--ignore-scripts or --omit=optional?); using this Node " + running.node };
|
|
208
|
+
}
|
|
209
|
+
return { ok: false, reason: "the bundled Node was not installed, and this Node " + running.node + " is older than AgEnD needs (" + engines + ")", recovery: recovery };
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// 4. No runtime for this host (unsupported, or none pinned in this release): the running Node, if it qualifies.
|
|
213
|
+
if (qualifies(running, engines)) return { ok: true, node: realpath(execPath) || execPath, source: "system", pkgDir: pkg.dir };
|
|
214
|
+
return { ok: false, reason: (pin ? support.reason : "this release bundles no Node") + "; this Node " + running.node + " is older than AgEnD needs (" + engines + ")", recovery: "install Node " + engines + ", then " + recovery };
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
module.exports = { KEY: KEY, cksum: cksum, runtimeKey: runtimeKey, keyMatches: keyMatches, RECEIPT: RECEIPT, MIN_NAPI: MIN_NAPI, selectRuntime: selectRuntime, packageDir: packageDir, pinnedRuntime: pinnedRuntime, runtimeCandidate: runtimeCandidate, probeNode: probeNode, qualifies: qualifies, receiptMatches: receiptMatches, readReceipt: readReceipt, absent: absent, receiptText: receiptText, RECEIPT_KEYS: RECEIPT_KEYS, PLAIN: PLAIN };
|
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@songsid/agend",
|
|
3
|
-
"version": "2.2.0-alpha.
|
|
3
|
+
"version": "2.2.0-alpha.3",
|
|
4
4
|
"description": "Multi-agent fleet daemon — run any coding CLI (Claude, Gemini, Codex, OpenCode) from Telegram",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
|
-
"agend": "./
|
|
8
|
-
"agend-agent": "./
|
|
7
|
+
"agend": "./launcher/agend",
|
|
8
|
+
"agend-agent": "./launcher/agend-agent"
|
|
9
9
|
},
|
|
10
10
|
"exports": {
|
|
11
11
|
".": "./dist/cli.js",
|
|
@@ -27,7 +27,8 @@
|
|
|
27
27
|
"typecheck": "tsc --noEmit",
|
|
28
28
|
"typecheck:tests": "tsc --noEmit -p tsconfig.test.json",
|
|
29
29
|
"prepublishOnly": "npm run build",
|
|
30
|
-
"preinstall": "node scripts/preinstall-guard.cjs"
|
|
30
|
+
"preinstall": "node scripts/preinstall-guard.cjs",
|
|
31
|
+
"postinstall": "node launcher/postinstall.cjs"
|
|
31
32
|
},
|
|
32
33
|
"engines": {
|
|
33
34
|
"node": "^22.14.0 || ^23.6.0 || >=24"
|
|
@@ -54,6 +55,7 @@
|
|
|
54
55
|
"templates/",
|
|
55
56
|
"README.md",
|
|
56
57
|
"README.zh-TW.md",
|
|
58
|
+
"launcher/",
|
|
57
59
|
"scripts/preinstall-guard.cjs"
|
|
58
60
|
],
|
|
59
61
|
"dependencies": {
|
|
@@ -72,6 +74,12 @@
|
|
|
72
74
|
"yaml": "^2.9.0",
|
|
73
75
|
"zod": "^4.3.6"
|
|
74
76
|
},
|
|
77
|
+
"optionalDependencies": {
|
|
78
|
+
"@songsid/agend-node-darwin-arm64": "22.23.3",
|
|
79
|
+
"@songsid/agend-node-darwin-x64": "22.23.3",
|
|
80
|
+
"@songsid/agend-node-linux-arm64": "22.23.3",
|
|
81
|
+
"@songsid/agend-node-linux-x64": "22.23.3"
|
|
82
|
+
},
|
|
75
83
|
"devDependencies": {
|
|
76
84
|
"@types/better-sqlite3": "^7.6.13",
|
|
77
85
|
"@types/ejs": "^3.1.5",
|
|
@@ -1,36 +1,55 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// preinstall-guard.cjs — runs as npm's `preinstall` lifecycle hook.
|
|
3
|
-
// No dependencies: only built-in Node.js APIs.
|
|
3
|
+
// No dependencies: only built-in Node.js APIs and the package's own launcher/ helpers.
|
|
4
4
|
//
|
|
5
5
|
// better-sqlite3 v13 requires Node-API 10, available from:
|
|
6
6
|
// Node 22.14.0 LTS (released 2025-02-06)
|
|
7
7
|
// Node 23.6.0 (released 2025-01-13)
|
|
8
8
|
// Node 24+
|
|
9
9
|
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
10
|
+
// #1450: where this release pins a bundled Node for a supported host (Linux glibc >= 2.28 or macOS 11+, x64/arm64),
|
|
11
|
+
// the system Node's version is NEVER a reason to refuse — 20, 18, 16, any Node whose npm can run the install: AgEnD
|
|
12
|
+
// will not run on it. preinstall cannot verify the bundled Node (it is not on disk yet); launcher/postinstall.cjs
|
|
13
|
+
// proves it inside the same npm transaction and refuses (npm rolls back, the previous install stays) if it cannot.
|
|
14
|
+
// The one refusal here: a host no bundled Node covers (musl, 32-bit, another OS, older glibc/macOS, or a release that
|
|
15
|
+
// pins none) AND a system Node older than ENGINES — AgEnD would install and then not start.
|
|
16
|
+
// Old syntax only (var, no ?. ??, no trailing call commas): this runs under whatever Node runs npm.
|
|
12
17
|
//
|
|
18
|
+
// Exiting 1 aborts the install and keeps the previously installed version intact.
|
|
13
19
|
// --ignore-scripts bypasses this check; see Upgrade Notes in the changelog.
|
|
14
20
|
|
|
15
21
|
"use strict";
|
|
16
22
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
(maj === 23 && min >= 6) ||
|
|
21
|
-
(maj === 22 && min >= 14);
|
|
23
|
+
var path = require("path");
|
|
24
|
+
var fs = require("fs");
|
|
25
|
+
var platform = require(path.join(__dirname, "..", "launcher", "runtime-platform.cjs"));
|
|
22
26
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
27
|
+
var ENGINES = "^22.14.0 || ^23.6.0 || >=24";
|
|
28
|
+
var running = process.versions.node;
|
|
29
|
+
var napi = Number(process.versions.napi);
|
|
30
|
+
|
|
31
|
+
if (platform.satisfiesEngines(running, ENGINES) && napi >= 10) process.exit(0);
|
|
32
|
+
|
|
33
|
+
var manifest = {};
|
|
34
|
+
try { manifest = JSON.parse(fs.readFileSync(path.join(__dirname, "..", "package.json"), "utf8")); } catch (e) { /* none */ }
|
|
35
|
+
var host = platform.hostPlatform();
|
|
36
|
+
var pinned = manifest.optionalDependencies && manifest.optionalDependencies["@songsid/agend-node-" + host.id];
|
|
37
|
+
var support = platform.runtimeSupport(host);
|
|
38
|
+
if (pinned && support.supported) {
|
|
39
|
+
// The bundled Node is pinned and fits this host: postinstall verifies it before the install can complete.
|
|
40
|
+
process.exit(0);
|
|
36
41
|
}
|
|
42
|
+
|
|
43
|
+
process.stderr.write(
|
|
44
|
+
"\n" +
|
|
45
|
+
" AgEnD 2.2 needs Node >=22.14; npm is aborting this install.\n" +
|
|
46
|
+
"\n" +
|
|
47
|
+
" Running: Node " + running + "\n" +
|
|
48
|
+
" Required: " + ENGINES + "\n" +
|
|
49
|
+
(pinned ? " " + support.reason + ", so AgEnD's bundled Node cannot be used here either.\n" : "") +
|
|
50
|
+
"\n" +
|
|
51
|
+
" Update Node to a compatible version, then retry:\n" +
|
|
52
|
+
" npm install -g @songsid/agend\n" +
|
|
53
|
+
"\n"
|
|
54
|
+
);
|
|
55
|
+
process.exit(1);
|
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
<string><%= label %></string>
|
|
7
7
|
<key>ProgramArguments</key>
|
|
8
8
|
<array>
|
|
9
|
-
<string><%=
|
|
10
|
-
<string>fleet</string>
|
|
9
|
+
<% for (const word of program) { %> <string><%= word %></string>
|
|
10
|
+
<% } %> <string>fleet</string>
|
|
11
11
|
<string>start</string>
|
|
12
12
|
</array>
|
|
13
13
|
<key>EnvironmentVariables</key>
|
|
@@ -22,16 +22,16 @@ NotifyAccess=all
|
|
|
22
22
|
# Bounded so a start that hangs before READY is noticed (#1113).
|
|
23
23
|
TimeoutStartSec=15min
|
|
24
24
|
WatchdogSec=60
|
|
25
|
-
ExecStart
|
|
25
|
+
ExecStart=<%- program.map(systemdQuote).join(" ") %> fleet start
|
|
26
26
|
Environment=PATH=<%= path %>
|
|
27
27
|
Environment=TERM=xterm-256color
|
|
28
28
|
<% if (isRoot) { %>Environment=IS_SANDBOX=1
|
|
29
29
|
<% } %>WorkingDirectory=<%= workingDirectory %>
|
|
30
30
|
Restart=on-failure
|
|
31
31
|
RestartSec=5
|
|
32
|
-
#
|
|
33
|
-
#
|
|
34
|
-
TimeoutStopSec
|
|
32
|
+
# Busy Kiro instances need 15s to drain + 5s quit + 2s signal grace in each
|
|
33
|
+
# sequential batch. Keep an outer bound, but allow several batches (#1071).
|
|
34
|
+
TimeoutStopSec=<%= stopTimeoutSec %>
|
|
35
35
|
# Stop by signalling the fleet process only (#908). The tmux server and every
|
|
36
36
|
# CLI inside it live in this unit's cgroup, and the default control-group mode
|
|
37
37
|
# SIGTERMs all of them at once: kiro-cli aborted (SIGABRT, a ~1 GB core dump on
|