local-commander-agent 0.3.53 → 0.3.54
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 +245 -239
- package/README.md +219 -216
- package/bin/command-path.mjs +223 -223
- package/bin/localcommander.mjs +874 -874
- package/bin/monitor-agents.mjs +162 -162
- package/bin/monitor-context.d.mts +7 -7
- package/bin/monitor-context.mjs +65 -65
- package/bin/monitor-mcp.mjs +136 -136
- package/bin/monitor-savings.d.mts +20 -20
- package/bin/monitor-savings.mjs +71 -71
- package/bin/monitor.mjs +530 -530
- package/bin/network-package.mjs +38 -38
- package/bin/posix.mjs +84 -84
- package/package.json +57 -57
- package/runtime/JobHost.exe +0 -0
- package/runtime/LocalCommanderAgent.exe +0 -0
- package/runtime/OwnerBridge.exe +0 -0
- package/runtime/agent.mjs +41 -8
package/bin/network-package.mjs
CHANGED
|
@@ -1,38 +1,38 @@
|
|
|
1
|
-
import fs from "node:fs/promises";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
import { createHash } from "node:crypto";
|
|
4
|
-
|
|
5
|
-
const HASH=/^[a-f0-9]{64}$/;
|
|
6
|
-
/** Signed bundles may be set up during install; an unsigned pilot only by an explicit owner command. */
|
|
7
|
-
export function implicitSetupAllowed(manifest){return manifest?.signed===true;}
|
|
8
|
-
const FILES=["JobHost.exe","libcronet.dll","sing-box.exe","wintun.dll","sing-box.LICENSE","wintun.LICENSE"];
|
|
9
|
-
async function plainDirectory(directory) {
|
|
10
|
-
const stat=await fs.lstat(directory);
|
|
11
|
-
if(!stat.isDirectory()||stat.isSymbolicLink()||(await fs.realpath(directory)).toLowerCase()!==path.resolve(directory).toLowerCase())throw Error("NETWORK_BUNDLE_DIRECTORY_INVALID");
|
|
12
|
-
}
|
|
13
|
-
async function bytes(file,max) {
|
|
14
|
-
const stat=await fs.lstat(file);
|
|
15
|
-
if(!stat.isFile()||stat.isSymbolicLink()||stat.size<1||stat.size>max)throw Error("NETWORK_BUNDLE_FILE_INVALID");
|
|
16
|
-
return fs.readFile(file);
|
|
17
|
-
}
|
|
18
|
-
export async function inspectNetworkBundle(directory) {
|
|
19
|
-
try {await plainDirectory(directory);}catch(error){if(error.code==="ENOENT")return null;throw error;}
|
|
20
|
-
const manifest=JSON.parse((await bytes(path.join(directory,"manifest.json"),8192)).toString("utf8"));
|
|
21
|
-
if(manifest.schemaVersion!==1||manifest.configured!==true||!(manifest.signed===true||manifest.signing==="unsigned-pilot")||!HASH.test(manifest.bundleId)||!HASH.test(manifest.helperSha256)||
|
|
22
|
-
!manifest.files||typeof manifest.files!=="object"||Array.isArray(manifest.files)||Object.keys(manifest.files).sort().join()!==[...FILES].sort().join())throw Error("NETWORK_BUNDLE_NOT_DEPLOYABLE");
|
|
23
|
-
const names=[...FILES,"NetworkService.exe","manifest.json"];
|
|
24
|
-
if((await fs.readdir(directory)).sort().join()!==names.sort().join())throw Error("NETWORK_BUNDLE_ALLOWLIST_FAILED");
|
|
25
|
-
for(const name of [...FILES,"NetworkService.exe"]){const expected=name==="NetworkService.exe"?manifest.helperSha256:manifest.files[name];
|
|
26
|
-
if(!HASH.test(expected)||createHash("sha256").update(await bytes(path.join(directory,name),128*1024*1024)).digest("hex")!==expected)throw Error("NETWORK_BUNDLE_DIGEST_MISMATCH");}
|
|
27
|
-
return {bundleId:manifest.bundleId,files:names,manifest};
|
|
28
|
-
}
|
|
29
|
-
export async function copyNetworkBundle(source,destination) {
|
|
30
|
-
const bundle=await inspectNetworkBundle(source);if(!bundle)return false;
|
|
31
|
-
if(path.resolve(source).toLowerCase()===path.resolve(destination).toLowerCase())return true;
|
|
32
|
-
const exists=await fs.lstat(destination).catch(e=>{if(e.code!=="ENOENT")throw e;return null;});
|
|
33
|
-
if(exists){const previous=await inspectNetworkBundle(destination);if(previous?.bundleId===bundle.bundleId&&previous.manifest.helperSha256===bundle.manifest.helperSha256)return true;throw Error("NETWORK_BUNDLE_DESTINATION_CONFLICT");}
|
|
34
|
-
await fs.mkdir(destination,{recursive:false,mode:0o700});
|
|
35
|
-
try{for(const name of bundle.files)await fs.copyFile(path.join(source,name),path.join(destination,name),fs.constants.COPYFILE_EXCL);await inspectNetworkBundle(destination);}
|
|
36
|
-
catch(error){for(const name of bundle.files)await fs.rm(path.join(destination,name),{force:true}).catch(()=>{});await fs.rmdir(destination).catch(()=>{});throw error;}
|
|
37
|
-
return true;
|
|
38
|
-
}
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { createHash } from "node:crypto";
|
|
4
|
+
|
|
5
|
+
const HASH=/^[a-f0-9]{64}$/;
|
|
6
|
+
/** Signed bundles may be set up during install; an unsigned pilot only by an explicit owner command. */
|
|
7
|
+
export function implicitSetupAllowed(manifest){return manifest?.signed===true;}
|
|
8
|
+
const FILES=["JobHost.exe","libcronet.dll","sing-box.exe","wintun.dll","sing-box.LICENSE","wintun.LICENSE"];
|
|
9
|
+
async function plainDirectory(directory) {
|
|
10
|
+
const stat=await fs.lstat(directory);
|
|
11
|
+
if(!stat.isDirectory()||stat.isSymbolicLink()||(await fs.realpath(directory)).toLowerCase()!==path.resolve(directory).toLowerCase())throw Error("NETWORK_BUNDLE_DIRECTORY_INVALID");
|
|
12
|
+
}
|
|
13
|
+
async function bytes(file,max) {
|
|
14
|
+
const stat=await fs.lstat(file);
|
|
15
|
+
if(!stat.isFile()||stat.isSymbolicLink()||stat.size<1||stat.size>max)throw Error("NETWORK_BUNDLE_FILE_INVALID");
|
|
16
|
+
return fs.readFile(file);
|
|
17
|
+
}
|
|
18
|
+
export async function inspectNetworkBundle(directory) {
|
|
19
|
+
try {await plainDirectory(directory);}catch(error){if(error.code==="ENOENT")return null;throw error;}
|
|
20
|
+
const manifest=JSON.parse((await bytes(path.join(directory,"manifest.json"),8192)).toString("utf8"));
|
|
21
|
+
if(manifest.schemaVersion!==1||manifest.configured!==true||!(manifest.signed===true||manifest.signing==="unsigned-pilot")||!HASH.test(manifest.bundleId)||!HASH.test(manifest.helperSha256)||
|
|
22
|
+
!manifest.files||typeof manifest.files!=="object"||Array.isArray(manifest.files)||Object.keys(manifest.files).sort().join()!==[...FILES].sort().join())throw Error("NETWORK_BUNDLE_NOT_DEPLOYABLE");
|
|
23
|
+
const names=[...FILES,"NetworkService.exe","manifest.json"];
|
|
24
|
+
if((await fs.readdir(directory)).sort().join()!==names.sort().join())throw Error("NETWORK_BUNDLE_ALLOWLIST_FAILED");
|
|
25
|
+
for(const name of [...FILES,"NetworkService.exe"]){const expected=name==="NetworkService.exe"?manifest.helperSha256:manifest.files[name];
|
|
26
|
+
if(!HASH.test(expected)||createHash("sha256").update(await bytes(path.join(directory,name),128*1024*1024)).digest("hex")!==expected)throw Error("NETWORK_BUNDLE_DIGEST_MISMATCH");}
|
|
27
|
+
return {bundleId:manifest.bundleId,files:names,manifest};
|
|
28
|
+
}
|
|
29
|
+
export async function copyNetworkBundle(source,destination) {
|
|
30
|
+
const bundle=await inspectNetworkBundle(source);if(!bundle)return false;
|
|
31
|
+
if(path.resolve(source).toLowerCase()===path.resolve(destination).toLowerCase())return true;
|
|
32
|
+
const exists=await fs.lstat(destination).catch(e=>{if(e.code!=="ENOENT")throw e;return null;});
|
|
33
|
+
if(exists){const previous=await inspectNetworkBundle(destination);if(previous?.bundleId===bundle.bundleId&&previous.manifest.helperSha256===bundle.manifest.helperSha256)return true;throw Error("NETWORK_BUNDLE_DESTINATION_CONFLICT");}
|
|
34
|
+
await fs.mkdir(destination,{recursive:false,mode:0o700});
|
|
35
|
+
try{for(const name of bundle.files)await fs.copyFile(path.join(source,name),path.join(destination,name),fs.constants.COPYFILE_EXCL);await inspectNetworkBundle(destination);}
|
|
36
|
+
catch(error){for(const name of bundle.files)await fs.rm(path.join(destination,name),{force:true}).catch(()=>{});await fs.rmdir(destination).catch(()=>{});throw error;}
|
|
37
|
+
return true;
|
|
38
|
+
}
|
package/bin/posix.mjs
CHANGED
|
@@ -1,84 +1,84 @@
|
|
|
1
|
-
// macOS and Linux: the runtime ships in a per-platform package (an optional dependency, so npm installs only the
|
|
2
|
-
// matching one). Its lifecycle accepts only a private, owner-only artifact directory, so the files are copied from the
|
|
3
|
-
// npx cache into a fresh private directory and the lifecycle installs from there into its own versions directory.
|
|
4
|
-
import { constants } from "node:fs";
|
|
5
|
-
import fs from "node:fs/promises";
|
|
6
|
-
import os from "node:os";
|
|
7
|
-
import path from "node:path";
|
|
8
|
-
import { spawnSync } from "node:child_process";
|
|
9
|
-
import { createRequire } from "node:module";
|
|
10
|
-
|
|
11
|
-
const PACKAGES = { "darwin-arm64": "local-commander-agent-darwin-arm64", "linux-x64": "local-commander-agent-linux-x64", "linux-arm64": "local-commander-agent-linux-arm64" };
|
|
12
|
-
// npm command -> lifecycle command. The default (`npx local-commander-agent@latest`) is setup, as on Windows.
|
|
13
|
-
const COMMANDS = { bootstrap: "setup", install: "setup", setup: "setup", update: "update", pair: "pair", access: "access", "configure-access": "access",
|
|
14
|
-
start: "start", stop: "stop", status: "status", rollback: "rollback", uninstall: "uninstall" };
|
|
15
|
-
const EXECUTABLES = new Set(["node", "posix-host", "owner-store"]);
|
|
16
|
-
|
|
17
|
-
function fail(code) { const error = new Error(code); error.code = code; throw error; }
|
|
18
|
-
|
|
19
|
-
export function posixHelp() {
|
|
20
|
-
return [
|
|
21
|
-
"Local Commander",
|
|
22
|
-
"",
|
|
23
|
-
" npx local-commander-agent@latest set up this computer, pair it and choose access",
|
|
24
|
-
" npx local-commander-agent@latest update install this version, keeping pairing and access",
|
|
25
|
-
" npx local-commander-agent@latest status|start|stop|pair|access|rollback",
|
|
26
|
-
" npx local-commander-agent@latest uninstall [--purge-state]",
|
|
27
|
-
""
|
|
28
|
-
].join("\n");
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
// Owner opt-out for take_screenshot under Full Access (#486): a marker in the installed service agent's state folder
|
|
32
|
-
// (the service runs without XDG_DATA_HOME). No runtime staging is needed; the agent checks it on each capture.
|
|
33
|
-
async function screenshotCommand(args) {
|
|
34
|
-
const action = args[0] || "status";
|
|
35
|
-
if (args.length > 1 || !["status", "on", "off"].includes(action)) fail("ARGUMENT_INVALID");
|
|
36
|
-
if (typeof process.getuid === "function" && process.getuid() === 0) fail("POSIX_OWNER_REQUIRED");
|
|
37
|
-
const state = path.join(os.homedir(), process.platform === "darwin" ? "Library/Application Support" : ".local/share", "LocalCommander", "Agent");
|
|
38
|
-
const folder = await fs.lstat(state).catch(() => null);
|
|
39
|
-
if (!folder?.isDirectory() || folder.uid !== process.getuid()) fail("AGENT_NOT_INSTALLED");
|
|
40
|
-
const file = path.join(state, "screenshot-disabled-by-owner");
|
|
41
|
-
if (action === "off") await fs.open(file, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | constants.O_NOFOLLOW, 0o600)
|
|
42
|
-
.then(handle => handle.close(), error => { if (error?.code !== "EEXIST") throw error; });
|
|
43
|
-
if (action === "on") await fs.rm(file, { force: true });
|
|
44
|
-
const off = await fs.lstat(file).then(() => true, error => error?.code !== "ENOENT");
|
|
45
|
-
process.stdout.write(JSON.stringify({ screenshot: off ? "off" : "on" }) + "\n");
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
export async function runPosix(argv, version) {
|
|
49
|
-
const name = PACKAGES[process.platform + "-" + process.arch];
|
|
50
|
-
if (!name) fail(process.platform === "darwin" ? "MACOS_APPLE_SILICON_REQUIRED" : process.platform === "linux" ? "LINUX_X64_OR_ARM64_REQUIRED" : "PLATFORM_UNSUPPORTED");
|
|
51
|
-
const command = argv[0] || "bootstrap", args = argv.slice(1);
|
|
52
|
-
if (command === "-h" || command === "--help" || command === "help") { process.stdout.write(posixHelp()); return; }
|
|
53
|
-
if (command === "screenshot") { await screenshotCommand(args); return; }
|
|
54
|
-
const lifecycle = COMMANDS[command];
|
|
55
|
-
if (!lifecycle) fail("ARGUMENT_INVALID");
|
|
56
|
-
if (lifecycle === "uninstall" ? args.length > 1 || (args.length === 1 && args[0] !== "--purge-state") : args.length) fail("ARGUMENT_INVALID");
|
|
57
|
-
if (typeof process.getuid === "function" && process.getuid() === 0) fail("POSIX_OWNER_REQUIRED");
|
|
58
|
-
|
|
59
|
-
let packageJson;
|
|
60
|
-
try { packageJson = createRequire(import.meta.url).resolve(name + "/package.json"); } catch { fail("PLATFORM_PACKAGE_MISSING"); }
|
|
61
|
-
const meta = JSON.parse(await fs.readFile(packageJson, "utf8"));
|
|
62
|
-
if (meta.name !== name || meta.version !== version) fail("PLATFORM_PACKAGE_MISMATCH");
|
|
63
|
-
const source = path.join(path.dirname(packageJson), "runtime");
|
|
64
|
-
const manifest = JSON.parse(await fs.readFile(path.join(source, "manifest.json"), "utf8"));
|
|
65
|
-
const files = Object.keys(manifest?.files ?? {});
|
|
66
|
-
if (manifest?.platform !== process.platform || manifest?.arch !== process.arch || !files.length || files.some(file => !/^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/u.test(file))) fail("PLATFORM_PACKAGE_INVALID");
|
|
67
|
-
|
|
68
|
-
// Canonical parent: on macOS os.tmpdir() is under /var, a symlink to /private/var, and the lifecycle rejects aliases.
|
|
69
|
-
const stage = await fs.mkdtemp(path.join(await fs.realpath(os.tmpdir()), "local-commander-"));
|
|
70
|
-
try {
|
|
71
|
-
await fs.chmod(stage, 0o700);
|
|
72
|
-
for (const file of [...files, "manifest.json"]) {
|
|
73
|
-
const target = path.join(stage, file);
|
|
74
|
-
await fs.copyFile(path.join(source, file), target, constants.COPYFILE_EXCL);
|
|
75
|
-
await fs.chmod(target, EXECUTABLES.has(file) ? 0o700 : 0o600);
|
|
76
|
-
}
|
|
77
|
-
// The lifecycle verifies every file against the manifest, then copies them into its own versions directory.
|
|
78
|
-
const result = spawnSync(path.join(stage, "node"), [path.join(stage, "lifecycle.mjs"), lifecycle, ...args], { stdio: "inherit" });
|
|
79
|
-
if (result.error) throw result.error;
|
|
80
|
-
process.exitCode = result.status ?? 1;
|
|
81
|
-
} finally {
|
|
82
|
-
await fs.rm(stage, { recursive: true, force: true });
|
|
83
|
-
}
|
|
84
|
-
}
|
|
1
|
+
// macOS and Linux: the runtime ships in a per-platform package (an optional dependency, so npm installs only the
|
|
2
|
+
// matching one). Its lifecycle accepts only a private, owner-only artifact directory, so the files are copied from the
|
|
3
|
+
// npx cache into a fresh private directory and the lifecycle installs from there into its own versions directory.
|
|
4
|
+
import { constants } from "node:fs";
|
|
5
|
+
import fs from "node:fs/promises";
|
|
6
|
+
import os from "node:os";
|
|
7
|
+
import path from "node:path";
|
|
8
|
+
import { spawnSync } from "node:child_process";
|
|
9
|
+
import { createRequire } from "node:module";
|
|
10
|
+
|
|
11
|
+
const PACKAGES = { "darwin-arm64": "local-commander-agent-darwin-arm64", "linux-x64": "local-commander-agent-linux-x64", "linux-arm64": "local-commander-agent-linux-arm64" };
|
|
12
|
+
// npm command -> lifecycle command. The default (`npx local-commander-agent@latest`) is setup, as on Windows.
|
|
13
|
+
const COMMANDS = { bootstrap: "setup", install: "setup", setup: "setup", update: "update", pair: "pair", access: "access", "configure-access": "access",
|
|
14
|
+
start: "start", stop: "stop", status: "status", rollback: "rollback", uninstall: "uninstall" };
|
|
15
|
+
const EXECUTABLES = new Set(["node", "posix-host", "owner-store"]);
|
|
16
|
+
|
|
17
|
+
function fail(code) { const error = new Error(code); error.code = code; throw error; }
|
|
18
|
+
|
|
19
|
+
export function posixHelp() {
|
|
20
|
+
return [
|
|
21
|
+
"Local Commander",
|
|
22
|
+
"",
|
|
23
|
+
" npx local-commander-agent@latest set up this computer, pair it and choose access",
|
|
24
|
+
" npx local-commander-agent@latest update install this version, keeping pairing and access",
|
|
25
|
+
" npx local-commander-agent@latest status|start|stop|pair|access|rollback",
|
|
26
|
+
" npx local-commander-agent@latest uninstall [--purge-state]",
|
|
27
|
+
""
|
|
28
|
+
].join("\n");
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Owner opt-out for take_screenshot under Full Access (#486): a marker in the installed service agent's state folder
|
|
32
|
+
// (the service runs without XDG_DATA_HOME). No runtime staging is needed; the agent checks it on each capture.
|
|
33
|
+
async function screenshotCommand(args) {
|
|
34
|
+
const action = args[0] || "status";
|
|
35
|
+
if (args.length > 1 || !["status", "on", "off"].includes(action)) fail("ARGUMENT_INVALID");
|
|
36
|
+
if (typeof process.getuid === "function" && process.getuid() === 0) fail("POSIX_OWNER_REQUIRED");
|
|
37
|
+
const state = path.join(os.homedir(), process.platform === "darwin" ? "Library/Application Support" : ".local/share", "LocalCommander", "Agent");
|
|
38
|
+
const folder = await fs.lstat(state).catch(() => null);
|
|
39
|
+
if (!folder?.isDirectory() || folder.uid !== process.getuid()) fail("AGENT_NOT_INSTALLED");
|
|
40
|
+
const file = path.join(state, "screenshot-disabled-by-owner");
|
|
41
|
+
if (action === "off") await fs.open(file, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | constants.O_NOFOLLOW, 0o600)
|
|
42
|
+
.then(handle => handle.close(), error => { if (error?.code !== "EEXIST") throw error; });
|
|
43
|
+
if (action === "on") await fs.rm(file, { force: true });
|
|
44
|
+
const off = await fs.lstat(file).then(() => true, error => error?.code !== "ENOENT");
|
|
45
|
+
process.stdout.write(JSON.stringify({ screenshot: off ? "off" : "on" }) + "\n");
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export async function runPosix(argv, version) {
|
|
49
|
+
const name = PACKAGES[process.platform + "-" + process.arch];
|
|
50
|
+
if (!name) fail(process.platform === "darwin" ? "MACOS_APPLE_SILICON_REQUIRED" : process.platform === "linux" ? "LINUX_X64_OR_ARM64_REQUIRED" : "PLATFORM_UNSUPPORTED");
|
|
51
|
+
const command = argv[0] || "bootstrap", args = argv.slice(1);
|
|
52
|
+
if (command === "-h" || command === "--help" || command === "help") { process.stdout.write(posixHelp()); return; }
|
|
53
|
+
if (command === "screenshot") { await screenshotCommand(args); return; }
|
|
54
|
+
const lifecycle = COMMANDS[command];
|
|
55
|
+
if (!lifecycle) fail("ARGUMENT_INVALID");
|
|
56
|
+
if (lifecycle === "uninstall" ? args.length > 1 || (args.length === 1 && args[0] !== "--purge-state") : args.length) fail("ARGUMENT_INVALID");
|
|
57
|
+
if (typeof process.getuid === "function" && process.getuid() === 0) fail("POSIX_OWNER_REQUIRED");
|
|
58
|
+
|
|
59
|
+
let packageJson;
|
|
60
|
+
try { packageJson = createRequire(import.meta.url).resolve(name + "/package.json"); } catch { fail("PLATFORM_PACKAGE_MISSING"); }
|
|
61
|
+
const meta = JSON.parse(await fs.readFile(packageJson, "utf8"));
|
|
62
|
+
if (meta.name !== name || meta.version !== version) fail("PLATFORM_PACKAGE_MISMATCH");
|
|
63
|
+
const source = path.join(path.dirname(packageJson), "runtime");
|
|
64
|
+
const manifest = JSON.parse(await fs.readFile(path.join(source, "manifest.json"), "utf8"));
|
|
65
|
+
const files = Object.keys(manifest?.files ?? {});
|
|
66
|
+
if (manifest?.platform !== process.platform || manifest?.arch !== process.arch || !files.length || files.some(file => !/^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/u.test(file))) fail("PLATFORM_PACKAGE_INVALID");
|
|
67
|
+
|
|
68
|
+
// Canonical parent: on macOS os.tmpdir() is under /var, a symlink to /private/var, and the lifecycle rejects aliases.
|
|
69
|
+
const stage = await fs.mkdtemp(path.join(await fs.realpath(os.tmpdir()), "local-commander-"));
|
|
70
|
+
try {
|
|
71
|
+
await fs.chmod(stage, 0o700);
|
|
72
|
+
for (const file of [...files, "manifest.json"]) {
|
|
73
|
+
const target = path.join(stage, file);
|
|
74
|
+
await fs.copyFile(path.join(source, file), target, constants.COPYFILE_EXCL);
|
|
75
|
+
await fs.chmod(target, EXECUTABLES.has(file) ? 0o700 : 0o600);
|
|
76
|
+
}
|
|
77
|
+
// The lifecycle verifies every file against the manifest, then copies them into its own versions directory.
|
|
78
|
+
const result = spawnSync(path.join(stage, "node"), [path.join(stage, "lifecycle.mjs"), lifecycle, ...args], { stdio: "inherit" });
|
|
79
|
+
if (result.error) throw result.error;
|
|
80
|
+
process.exitCode = result.status ?? 1;
|
|
81
|
+
} finally {
|
|
82
|
+
await fs.rm(stage, { recursive: true, force: true });
|
|
83
|
+
}
|
|
84
|
+
}
|
package/package.json
CHANGED
|
@@ -1,57 +1,57 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "local-commander-agent",
|
|
3
|
-
"version": "0.3.
|
|
4
|
-
"description": "Computer companion for Local Commander: connect AI clients to local files, approved commands and localhost. Requires https://localcmd.app.",
|
|
5
|
-
"type": "module",
|
|
6
|
-
"bin": {
|
|
7
|
-
"localcommander": "bin/localcommander.mjs"
|
|
8
|
-
},
|
|
9
|
-
"os": [
|
|
10
|
-
"win32",
|
|
11
|
-
"darwin",
|
|
12
|
-
"linux"
|
|
13
|
-
],
|
|
14
|
-
"cpu": [
|
|
15
|
-
"x64",
|
|
16
|
-
"arm64"
|
|
17
|
-
],
|
|
18
|
-
"optionalDependencies": {
|
|
19
|
-
"local-commander-agent-darwin-arm64": "0.3.
|
|
20
|
-
"local-commander-agent-linux-arm64": "0.3.
|
|
21
|
-
"local-commander-agent-linux-x64": "0.3.
|
|
22
|
-
},
|
|
23
|
-
"engines": {
|
|
24
|
-
"node": ">=20.0.0"
|
|
25
|
-
},
|
|
26
|
-
"files": [
|
|
27
|
-
"bin/",
|
|
28
|
-
"runtime/",
|
|
29
|
-
"README.md",
|
|
30
|
-
"CHANGELOG.md"
|
|
31
|
-
],
|
|
32
|
-
"scripts": {
|
|
33
|
-
"prepack": "node ../../scripts/build-npm-agent.mjs",
|
|
34
|
-
"prepublishOnly": "node ../../scripts/check-npm-publish-auth.mjs && node ../../scripts/verify-npm-agent.mjs"
|
|
35
|
-
},
|
|
36
|
-
"publishConfig": {
|
|
37
|
-
"access": "public"
|
|
38
|
-
},
|
|
39
|
-
"keywords": [
|
|
40
|
-
"mcp",
|
|
41
|
-
"local",
|
|
42
|
-
"agent",
|
|
43
|
-
"chatgpt",
|
|
44
|
-
"claude",
|
|
45
|
-
"windows",
|
|
46
|
-
"macos",
|
|
47
|
-
"linux"
|
|
48
|
-
],
|
|
49
|
-
"localCommander": {
|
|
50
|
-
"controlPlaneOrigin": "https://localcmd.app"
|
|
51
|
-
},
|
|
52
|
-
"homepage": "https://localcmd.app",
|
|
53
|
-
"bugs": {
|
|
54
|
-
"url": "https://localcmd.app/support",
|
|
55
|
-
"email": "peter.mamrukov@localcmd.app"
|
|
56
|
-
}
|
|
57
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "local-commander-agent",
|
|
3
|
+
"version": "0.3.54",
|
|
4
|
+
"description": "Computer companion for Local Commander: connect AI clients to local files, approved commands and localhost. Requires https://localcmd.app.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"localcommander": "bin/localcommander.mjs"
|
|
8
|
+
},
|
|
9
|
+
"os": [
|
|
10
|
+
"win32",
|
|
11
|
+
"darwin",
|
|
12
|
+
"linux"
|
|
13
|
+
],
|
|
14
|
+
"cpu": [
|
|
15
|
+
"x64",
|
|
16
|
+
"arm64"
|
|
17
|
+
],
|
|
18
|
+
"optionalDependencies": {
|
|
19
|
+
"local-commander-agent-darwin-arm64": "0.3.54",
|
|
20
|
+
"local-commander-agent-linux-arm64": "0.3.54",
|
|
21
|
+
"local-commander-agent-linux-x64": "0.3.54"
|
|
22
|
+
},
|
|
23
|
+
"engines": {
|
|
24
|
+
"node": ">=20.0.0"
|
|
25
|
+
},
|
|
26
|
+
"files": [
|
|
27
|
+
"bin/",
|
|
28
|
+
"runtime/",
|
|
29
|
+
"README.md",
|
|
30
|
+
"CHANGELOG.md"
|
|
31
|
+
],
|
|
32
|
+
"scripts": {
|
|
33
|
+
"prepack": "node ../../scripts/build-npm-agent.mjs",
|
|
34
|
+
"prepublishOnly": "node ../../scripts/check-npm-publish-auth.mjs && node ../../scripts/verify-npm-agent.mjs"
|
|
35
|
+
},
|
|
36
|
+
"publishConfig": {
|
|
37
|
+
"access": "public"
|
|
38
|
+
},
|
|
39
|
+
"keywords": [
|
|
40
|
+
"mcp",
|
|
41
|
+
"local",
|
|
42
|
+
"agent",
|
|
43
|
+
"chatgpt",
|
|
44
|
+
"claude",
|
|
45
|
+
"windows",
|
|
46
|
+
"macos",
|
|
47
|
+
"linux"
|
|
48
|
+
],
|
|
49
|
+
"localCommander": {
|
|
50
|
+
"controlPlaneOrigin": "https://localcmd.app"
|
|
51
|
+
},
|
|
52
|
+
"homepage": "https://localcmd.app",
|
|
53
|
+
"bugs": {
|
|
54
|
+
"url": "https://localcmd.app/support",
|
|
55
|
+
"email": "peter.mamrukov@localcmd.app"
|
|
56
|
+
}
|
|
57
|
+
}
|
package/runtime/JobHost.exe
CHANGED
|
Binary file
|
|
Binary file
|
package/runtime/OwnerBridge.exe
CHANGED
|
Binary file
|
package/runtime/agent.mjs
CHANGED
|
@@ -27137,11 +27137,13 @@ var NativeJournal = class _NativeJournal {
|
|
|
27137
27137
|
}
|
|
27138
27138
|
/** The journal lock is only taken over once the previous agent process has exited, so its in-process work is gone.
|
|
27139
27139
|
* An interrupted entry is AGENT_RESTARTED (stopped, outcome unknown) when it recorded its supervisors and none is
|
|
27140
|
-
* still alive; otherwise, including every entry from an older agent, it stays EXECUTION_UNKNOWN (#194).
|
|
27140
|
+
* still alive; otherwise, including every entry from an older agent, it stays EXECUTION_UNKNOWN (#194).
|
|
27141
|
+
* Previously reported unknown work gets the same proof check on a later startup, never on a mere reconnect. */
|
|
27141
27142
|
recoverAfterRestart(liveness) {
|
|
27142
27143
|
return this.enqueue(async () => {
|
|
27143
27144
|
let count4 = 0;
|
|
27144
|
-
const
|
|
27145
|
+
const retryUnknown = (e) => e.state === "unknown" && e.resultCode === "EXECUTION_UNKNOWN" && !!e.workspaceGrantId && !!e.policyVersion && e.supervisors !== void 0;
|
|
27146
|
+
const interrupted = [...this.entries.values()].filter((e) => ["accepted", "running"].includes(e.state) || retryUnknown(e));
|
|
27145
27147
|
const pids = [...new Set(interrupted.flatMap((e) => e.supervisors ?? []))];
|
|
27146
27148
|
const linuxSupervisors = interrupted.flatMap((e) => e.linuxSupervisors ?? []), darwinSupervisors = interrupted.flatMap((e) => e.darwinSupervisors ?? []);
|
|
27147
27149
|
let live = /* @__PURE__ */ new Set();
|
|
@@ -27154,11 +27156,19 @@ var NativeJournal = class _NativeJournal {
|
|
|
27154
27156
|
}
|
|
27155
27157
|
for (const original of this.entries.values()) {
|
|
27156
27158
|
const stranded = original.state === "prepared";
|
|
27157
|
-
|
|
27159
|
+
const retry = retryUnknown(original);
|
|
27160
|
+
if (!stranded && !["accepted", "running"].includes(original.state) && !retry) continue;
|
|
27158
27161
|
const e = clone2(original);
|
|
27159
27162
|
e.state = stranded ? "rejected" : "unknown";
|
|
27160
27163
|
const stopped2 = !!e.supervisors && (e.supervisors.length === 0 || !!live && e.supervisors.every((pid) => !live.has(pid)));
|
|
27161
|
-
if (
|
|
27164
|
+
if (retry && !stopped2) continue;
|
|
27165
|
+
if (!stranded && e.workspaceGrantId && e.policyVersion) {
|
|
27166
|
+
e.resultCode = stopped2 ? "AGENT_RESTARTED" : "EXECUTION_UNKNOWN";
|
|
27167
|
+
if (stopped2) {
|
|
27168
|
+
delete e.reportedAt;
|
|
27169
|
+
if (e.result) e.result = { ...e.result, treeTerminated: true };
|
|
27170
|
+
}
|
|
27171
|
+
}
|
|
27162
27172
|
e.updatedAt = this.now().toISOString();
|
|
27163
27173
|
const last = e.guardEvents?.at(-1);
|
|
27164
27174
|
if (last && last.event.phase !== "result") {
|
|
@@ -29233,6 +29243,27 @@ async function resolvePosixExecutable(command, searchPath = process.env.PATH ??
|
|
|
29233
29243
|
// agent/src/job-supervisor.ts
|
|
29234
29244
|
import { spawn as spawn4 } from "node:child_process";
|
|
29235
29245
|
import path16 from "node:path";
|
|
29246
|
+
var supervisorFailureCodes = /* @__PURE__ */ new Set([
|
|
29247
|
+
"SUPERVISOR_PLATFORM_UNSUPPORTED",
|
|
29248
|
+
"INVALID_SUPERVISOR_PATH",
|
|
29249
|
+
"INVALID_SUPERVISOR_ARGS",
|
|
29250
|
+
"INVALID_SUPERVISOR_LIMIT",
|
|
29251
|
+
"SUPERVISOR_INPUT_LIMIT",
|
|
29252
|
+
"SUPERVISOR_CONTROL_CLOSED",
|
|
29253
|
+
"SUPERVISOR_START_FAILED",
|
|
29254
|
+
"SUPERVISOR_DIAGNOSTIC_FAILURE",
|
|
29255
|
+
"SUPERVISOR_PROTOCOL_INVALID",
|
|
29256
|
+
"SUPERVISOR_PROTOCOL_LIMIT",
|
|
29257
|
+
"SUPERVISOR_EXECUTION_UNKNOWN",
|
|
29258
|
+
"SUPERVISOR_DEADLINE_EXCEEDED"
|
|
29259
|
+
]);
|
|
29260
|
+
var nativeFailureCode = /^(WIN32_[0-9]{1,10}|EXECUTION_OUTCOME_UNKNOWN|INVALID_OR_FAILED_START)$/;
|
|
29261
|
+
function supervisorFailureDiagnostic(error62) {
|
|
29262
|
+
if (!(error62 instanceof Error) || !supervisorFailureCodes.has(error62.message)) return null;
|
|
29263
|
+
const stage = typeof error62.cause === "string" && /^(REQUEST|BRIDGE|CGROUP|EXECUTION|CLEANUP)$/.test(error62.cause) ? error62.cause : void 0;
|
|
29264
|
+
const nativeCode = typeof error62.cause === "string" && nativeFailureCode.test(error62.cause) ? error62.cause : void 0;
|
|
29265
|
+
return { code: error62.message, ...stage ? { stage } : {}, ...nativeCode ? { nativeCode } : {} };
|
|
29266
|
+
}
|
|
29236
29267
|
function absolute(value) {
|
|
29237
29268
|
return typeof value === "string" && value.length > 0 && value.length < 32767 && !value.includes("\0") && path16.isAbsolute(value) && path16.resolve(value) === value;
|
|
29238
29269
|
}
|
|
@@ -29316,7 +29347,7 @@ async function runSupervised(helper, request, options = {}) {
|
|
|
29316
29347
|
if (pid === null || !Number.isInteger(m.exitCode) || typeof m.timedOut !== "boolean" || typeof m.cancelled !== "boolean" || m.treeTerminated !== true) throw Error();
|
|
29317
29348
|
outcome = { exitCode: m.exitCode, timedOut: m.timedOut, cancelled: m.cancelled, treeTerminated: true };
|
|
29318
29349
|
} else if (m.type === "error") {
|
|
29319
|
-
fail7("SUPERVISOR_EXECUTION_UNKNOWN", typeof m.stage === "string" && /^(REQUEST|BRIDGE|CGROUP|EXECUTION|CLEANUP)$/.test(m.stage) ? m.stage : void 0);
|
|
29350
|
+
fail7("SUPERVISOR_EXECUTION_UNKNOWN", typeof m.stage === "string" && /^(REQUEST|BRIDGE|CGROUP|EXECUTION|CLEANUP)$/.test(m.stage) ? m.stage : typeof m.code === "string" && nativeFailureCode.test(m.code) ? m.code : void 0);
|
|
29320
29351
|
return;
|
|
29321
29352
|
} else throw Error();
|
|
29322
29353
|
} catch {
|
|
@@ -32273,7 +32304,7 @@ var NativeRuntime = class {
|
|
|
32273
32304
|
if (!stored.created) {
|
|
32274
32305
|
if (!journalBinding(stored.entry, binding2)) throw Error("IDEMPOTENCY_CONFLICT");
|
|
32275
32306
|
if (stored.entry.result) {
|
|
32276
|
-
this.send({ type: "result", version: 1, binding: binding2, result: await outboundResult(frame.tool, journalResult(stored.entry.result), binding2, this.optionalTelemetry, this.options.efficiencyLedger) });
|
|
32307
|
+
this.send({ type: "result", version: 1, binding: binding2, result: await outboundResult(frame.tool, journalResult(stored.entry.result, stored.entry.resultCode), binding2, this.optionalTelemetry, this.options.efficiencyLedger) });
|
|
32277
32308
|
return;
|
|
32278
32309
|
}
|
|
32279
32310
|
if (stored.entry.state === "prepared") {
|
|
@@ -32372,6 +32403,8 @@ var NativeRuntime = class {
|
|
|
32372
32403
|
} else {
|
|
32373
32404
|
const isCommand = frame.tool.name === "command_run" || frame.tool.name.startsWith("git_") || frame.tool.name === "project_snapshot";
|
|
32374
32405
|
const uncertain = error62 instanceof Error && error62.message === "GUARD_EVIDENCE_FAILED" || isCommand && !(error62 instanceof NativeToolError);
|
|
32406
|
+
const supervisorFailure = supervisorFailureDiagnostic(error62);
|
|
32407
|
+
if (supervisorFailure) context.stderr.append("\n" + JSON.stringify({ supervisorFailure }) + "\n");
|
|
32375
32408
|
const output2 = { exitCode: null, timedOut: false, cancelled: abort.signal.aborted, treeTerminated: !uncertain, stdout: context.stdout.toString(), stderr: context.stderr.toString(), stdoutTruncated: context.stdout.truncated, stderrTruncated: context.stderr.truncated, resultCode: uncertain ? "EXECUTION_UNKNOWN" : abort.signal.aborted ? "CANCELLED" : safeCode(error62) };
|
|
32376
32409
|
try {
|
|
32377
32410
|
const { resultCode, ...raw } = output2;
|
|
@@ -32412,7 +32445,7 @@ var NativeRuntime = class {
|
|
|
32412
32445
|
const fresh = await this.options.journal.get(binding2.executionId);
|
|
32413
32446
|
let result2;
|
|
32414
32447
|
if (fresh?.result) {
|
|
32415
|
-
result2 = this.completedResults.get(binding2.executionId) ?? (context ? await outboundResult(context.tool.call, journalResult(fresh.result), binding2, this.optionalTelemetry, this.options.efficiencyLedger) : journalResult(fresh.result));
|
|
32448
|
+
result2 = this.completedResults.get(binding2.executionId) ?? (context ? await outboundResult(context.tool.call, journalResult(fresh.result, fresh.resultCode), binding2, this.optionalTelemetry, this.options.efficiencyLedger) : journalResult(fresh.result, fresh.resultCode));
|
|
32416
32449
|
if (frame.action === "output" && this.options.efficiencyLedger) {
|
|
32417
32450
|
const notice = await this.optionalTelemetry.run(() => this.options.efficiencyLedger.consumeNoticeForExecution?.(binding2.executionId), null);
|
|
32418
32451
|
if (notice) result2 = { ...result2, efficiencyNotice: notice };
|
|
@@ -51358,7 +51391,7 @@ async function restoreContentAudit(input2, output2, key) {
|
|
|
51358
51391
|
}
|
|
51359
51392
|
|
|
51360
51393
|
// agent/src/main.ts
|
|
51361
|
-
var AGENT_VERSION2 = true ? "0.3.
|
|
51394
|
+
var AGENT_VERSION2 = true ? "0.3.54" : "0.3.2";
|
|
51362
51395
|
var NETWORK_PUBLIC_KEY = true ? "-----BEGIN PUBLIC KEY-----\nMIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAvmu+81vWtJBQDZNHcAIW\nzCSxpG+R9RBsum2df5mP2cOW+wyUDTpnYquFge1qgI5YYTF96j0xCyXL8B9FeNWs\n939opWNmv53j+CQlS4cEIQhBbZSCu0eC4icsVEzBhvf68MB+E668SOtZBcVTEnyh\n96PTcupxS10rQJBWDinbKBXRC2gvGYeu01rWa/nsrndy4vAYP5t3yQtdOC8e8mr0\n+2Mg2Qhiq+KiTCtDd+L9oXvoLqqJYbzUIveNGha25AWxF1Weblp0gOM87hell5gV\nK7q4ZLUq/wBRGYNmQRdLyV7/WRE1FFPSW21/SBZQ/5aU4UqJU/MjMcrJUrnRKngz\nYKmo0czkxS3N7Q2KVxomgR1AQSuQFKMrboZYKjShADp9RIYUxzs7cYg51Of5PnHh\n6Ax+VevDGfzjMD+yHuyxYZqCtX4OwgolJxz63UQbzKlris+8xb5lLYTbIuz90Pss\nn6HK/F5May25c6oMmS3nh/NsiZopL1OvdtWKTt1L4WlRAgMBAAE=\n-----END PUBLIC KEY-----\n" : "";
|
|
51363
51396
|
var AI_CONNECTION_SHIPPED = false;
|
|
51364
51397
|
var QUARANTINE_RETRY_MS = 6e4;
|