@addai/node 0.18.0 → 0.19.0
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/dist/command-runner.js
CHANGED
|
@@ -64,6 +64,7 @@ const docker_1 = require("./desktop/docker");
|
|
|
64
64
|
const manager_1 = require("./desktop/manager");
|
|
65
65
|
const creds_1 = require("./desktop/creds");
|
|
66
66
|
const install_engine_1 = require("./desktop/install-engine");
|
|
67
|
+
const engine_1 = require("./desktop/engine");
|
|
67
68
|
const COMMAND_TIMEOUT_MS = 10 * 60 * 1000;
|
|
68
69
|
/** Own version, read the same way index.ts does. Resolved here rather than
|
|
69
70
|
* imported from index.ts, which already imports this module. */
|
|
@@ -692,7 +693,11 @@ async function runInstallEngine(cmd) {
|
|
|
692
693
|
};
|
|
693
694
|
await update(cmd.id, 'running', { log });
|
|
694
695
|
try {
|
|
695
|
-
|
|
696
|
+
// Installed but unreachable is a permissions problem, not a missing one -
|
|
697
|
+
// the same button must repair rather than reinstall.
|
|
698
|
+
const before = await (0, engine_1.detectEngine)();
|
|
699
|
+
const blocked = !!before && !before.running;
|
|
700
|
+
const outcome = await (0, install_engine_1.installEngine)(onLog, async () => {
|
|
696
701
|
// Park the command and let Studio collect it, exactly as a harness
|
|
697
702
|
// login collects a paste-back code.
|
|
698
703
|
await update(cmd.id, 'awaiting_input', { needs: 'sudo_password', log });
|
|
@@ -720,8 +725,19 @@ async function runInstallEngine(cmd) {
|
|
|
720
725
|
await new Promise(r => setTimeout(r, INPUT_POLL_MS));
|
|
721
726
|
}
|
|
722
727
|
return null;
|
|
723
|
-
});
|
|
728
|
+
}, blocked);
|
|
724
729
|
(0, docker_1.resetProviderCache)();
|
|
730
|
+
if (outcome.needsRestart) {
|
|
731
|
+
// Group membership only applies to new processes, so this daemon still
|
|
732
|
+
// cannot reach the socket. Say so plainly and bounce it.
|
|
733
|
+
onLog('\nPermissions fixed. Restarting this machine\'s node so it picks up the new group…\n');
|
|
734
|
+
await update(cmd.id, 'completed', { log, restarting: true });
|
|
735
|
+
await (0, heartbeat_1.beat)();
|
|
736
|
+
if (restartHook) {
|
|
737
|
+
await restartHook({ commandId: cmd.id, version: VERSION, restartOnly: true });
|
|
738
|
+
}
|
|
739
|
+
return;
|
|
740
|
+
}
|
|
725
741
|
const engine = await (0, docker_1.getProvider)(true);
|
|
726
742
|
if (!engine) {
|
|
727
743
|
await update(cmd.id, 'failed', { log }, 'Installed, but no engine is answering yet. On macOS, Docker Desktop has to be opened once before its daemon runs.');
|
|
@@ -8,6 +8,16 @@ export interface InstallPlan {
|
|
|
8
8
|
}
|
|
9
9
|
/** Pure, so the choice is testable on any machine. */
|
|
10
10
|
export declare function installPlan(platform: NodeJS.Platform, isRoot: boolean): InstallPlan;
|
|
11
|
+
/** Docker is there but this user cannot reach its socket. The fix is group
|
|
12
|
+
* membership, not another install — and it needs root, so it takes the same
|
|
13
|
+
* password round trip. */
|
|
14
|
+
export declare function repairPlan(user: string): {
|
|
15
|
+
inner: string;
|
|
16
|
+
manual: string;
|
|
17
|
+
};
|
|
18
|
+
/** Group membership is fixed for FUTURE processes only, so the daemon that
|
|
19
|
+
* asked for the fix still cannot reach the socket until it restarts. */
|
|
20
|
+
export declare const REPAIR_NEEDS_RESTART = true;
|
|
11
21
|
export declare function runningAsRoot(): boolean;
|
|
12
22
|
/** Build the argv for a privileged install. The password NEVER goes in argv —
|
|
13
23
|
* argv is world-readable through /proc on Linux, so anyone with a shell on
|
|
@@ -17,4 +27,9 @@ export declare function sudoArgv(inner: string): {
|
|
|
17
27
|
file: string;
|
|
18
28
|
args: string[];
|
|
19
29
|
};
|
|
20
|
-
|
|
30
|
+
/** Install if it is missing; fix permissions if it is merely unreachable.
|
|
31
|
+
* One button, because from the outside they are the same complaint: this
|
|
32
|
+
* machine cannot host desktops. Returns whether a restart is now needed. */
|
|
33
|
+
export declare function installEngine(onLog: (s: string) => void, askPassword?: () => Promise<string | null>, alreadyInstalledButBlocked?: boolean): Promise<{
|
|
34
|
+
needsRestart: boolean;
|
|
35
|
+
}>;
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.REPAIR_NEEDS_RESTART = void 0;
|
|
3
4
|
exports.installPlan = installPlan;
|
|
5
|
+
exports.repairPlan = repairPlan;
|
|
4
6
|
exports.runningAsRoot = runningAsRoot;
|
|
5
7
|
exports.sudoArgv = sudoArgv;
|
|
6
8
|
exports.installEngine = installEngine;
|
|
@@ -37,6 +39,19 @@ function installPlan(platform, isRoot) {
|
|
|
37
39
|
manual: 'curl -fsSL https://get.docker.com | sudo sh', needsRoot: true,
|
|
38
40
|
};
|
|
39
41
|
}
|
|
42
|
+
/** Docker is there but this user cannot reach its socket. The fix is group
|
|
43
|
+
* membership, not another install — and it needs root, so it takes the same
|
|
44
|
+
* password round trip. */
|
|
45
|
+
function repairPlan(user) {
|
|
46
|
+
const u = user.replace(/[^A-Za-z0-9._-]/g, '');
|
|
47
|
+
return {
|
|
48
|
+
inner: `usermod -aG docker ${u}`,
|
|
49
|
+
manual: `sudo usermod -aG docker ${u}`,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
/** Group membership is fixed for FUTURE processes only, so the daemon that
|
|
53
|
+
* asked for the fix still cannot reach the socket until it restarts. */
|
|
54
|
+
exports.REPAIR_NEEDS_RESTART = true;
|
|
40
55
|
function runningAsRoot() {
|
|
41
56
|
return typeof process.getuid === 'function' && process.getuid() === 0;
|
|
42
57
|
}
|
|
@@ -47,11 +62,37 @@ function runningAsRoot() {
|
|
|
47
62
|
function sudoArgv(inner) {
|
|
48
63
|
return { file: 'sudo', args: ['-S', '-p', '', 'sh', '-c', inner] };
|
|
49
64
|
}
|
|
50
|
-
|
|
65
|
+
/** Install if it is missing; fix permissions if it is merely unreachable.
|
|
66
|
+
* One button, because from the outside they are the same complaint: this
|
|
67
|
+
* machine cannot host desktops. Returns whether a restart is now needed. */
|
|
68
|
+
async function installEngine(onLog, askPassword, alreadyInstalledButBlocked) {
|
|
51
69
|
const plan = installPlan(process.platform, runningAsRoot());
|
|
52
70
|
let file = plan.file;
|
|
53
71
|
let args = plan.args;
|
|
54
72
|
let password = null;
|
|
73
|
+
// Docker present but the socket is refused: repair, do not reinstall.
|
|
74
|
+
const repair = alreadyInstalledButBlocked
|
|
75
|
+
? repairPlan(process.env.USER || process.env.LOGNAME || 'root')
|
|
76
|
+
: null;
|
|
77
|
+
if (repair) {
|
|
78
|
+
if (runningAsRoot()) {
|
|
79
|
+
onLog(`fixing permissions\n ${repair.inner}\n\n`);
|
|
80
|
+
file = 'sh';
|
|
81
|
+
args = ['-c', repair.inner];
|
|
82
|
+
}
|
|
83
|
+
else if (askPassword) {
|
|
84
|
+
onLog(`This machine needs a sudo password to fix Docker permissions.\n ${repair.manual}\n\n`);
|
|
85
|
+
password = await askPassword();
|
|
86
|
+
if (!password)
|
|
87
|
+
throw new Error(`No password given. Run it on the machine instead:\n ${repair.manual}`);
|
|
88
|
+
const sudo = sudoArgv(repair.inner);
|
|
89
|
+
file = sudo.file;
|
|
90
|
+
args = sudo.args;
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
throw new Error(`Needs root. Run this on the machine:\n ${repair.manual}`);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
55
96
|
if (!file && plan.needsRoot && askPassword) {
|
|
56
97
|
onLog('This machine needs a sudo password to install Docker.\n');
|
|
57
98
|
password = await askPassword();
|
|
@@ -86,6 +127,7 @@ async function installEngine(onLog, askPassword) {
|
|
|
86
127
|
child.on('error', err => reject(new Error(`${file} is not available here. Install it by hand:\n ${plan.manual}\n(${err.message})`)));
|
|
87
128
|
child.on('exit', code => code === 0
|
|
88
129
|
? resolve()
|
|
89
|
-
: reject(new Error(
|
|
130
|
+
: reject(new Error(`${repair ? 'permission fix' : 'install'} failed (exit ${code}). Try by hand:\n ${repair ? repair.manual : plan.manual}\n${tail.slice(-400)}`)));
|
|
90
131
|
});
|
|
132
|
+
return { needsRestart: !!repair && exports.REPAIR_NEEDS_RESTART };
|
|
91
133
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@addai/node",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.19.0",
|
|
4
4
|
"description": "Daemon that pairs a machine with your +Ai account and runs Claude / Codex / Kimi / Gemini agents on its behalf. Reachable via Supabase from Vault, Entity Studio, or any other +Ai surface.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"keywords": [
|