local-commander-agent 0.3.47 → 0.3.49

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.
@@ -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.47",
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.47",
20
- "local-commander-agent-linux-arm64": "0.3.47",
21
- "local-commander-agent-linux-x64": "0.3.47"
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.49",
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.49",
20
+ "local-commander-agent-linux-arm64": "0.3.49",
21
+ "local-commander-agent-linux-x64": "0.3.49"
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
+ }
Binary file
Binary file
Binary file
package/runtime/agent.mjs CHANGED
@@ -27932,11 +27932,16 @@ var WindowsPrivateStore = class _WindowsPrivateStore {
27932
27932
  if (process.platform !== "win32") for (const name2 of ["owner-key.json", "owner-key.dat", "owner-key.lock"]) approved.add(name2);
27933
27933
  if (names.length && !names.includes(MARKER)) throw fail2("STORE_DIRECTORY_UNKNOWN");
27934
27934
  const memoryFile = (name2) => name2 === "host-memory.json" || /^host-memory\.json\.[1-9][0-9]{0,9}\.tmp$/.test(name2);
27935
- if (names.some((name2) => !approved.has(name2) && !memoryFile(name2))) throw fail2("STORE_DIRECTORY_UNKNOWN");
27935
+ const switchFile = (name2) => name2 === "screenshot-acceptance-enabled" || name2 === "screenshot-disabled-by-owner";
27936
+ if (names.some((name2) => !approved.has(name2) && !memoryFile(name2) && !switchFile(name2))) throw fail2("STORE_DIRECTORY_UNKNOWN");
27936
27937
  for (const name2 of names.filter(memoryFile)) {
27937
27938
  const stat2 = await lstat4(path10.join(absolute2, name2));
27938
27939
  if (!stat2.isFile() || stat2.isSymbolicLink() || stat2.size > 32768) throw fail2("STORE_DIRECTORY_INVALID");
27939
27940
  }
27941
+ for (const name2 of names.filter(switchFile)) {
27942
+ const stat2 = await lstat4(path10.join(absolute2, name2));
27943
+ if (!stat2.isFile() || stat2.isSymbolicLink() || stat2.size > 4096) throw fail2("STORE_DIRECTORY_INVALID");
27944
+ }
27940
27945
  for (const name2 of ["agent-process.json", "optimization", ".local-commander"]) {
27941
27946
  if (!names.includes(name2)) continue;
27942
27947
  const filename = path10.join(absolute2, name2), stat2 = await lstat4(filename);
@@ -31100,7 +31105,7 @@ var NativeTools = class {
31100
31105
  if (call.name === "command_run") {
31101
31106
  if (!["win32", "linux", "darwin"].includes(process.platform)) fail3("COMMAND_NOT_ALLOWED");
31102
31107
  const cwd2 = await resolve(call.args.cwd, true), program = await this.resolveProgram(call.args.command, workspace2, call.args.args);
31103
- const claims2 = writeClaim(cwd2);
31108
+ const claims2 = call.args.cwd === void 0 ? [{ path: cwd2, namespace: "process", mode: "read" }] : writeClaim(cwd2);
31104
31109
  return { call, claims: claims2, execution: { ...program, cwd: cwd2 }, run: async (signal, onStarted, onOutput, onSupervisor) => {
31105
31110
  let r;
31106
31111
  try {
@@ -31368,7 +31373,10 @@ var NativeScheduler = class {
31368
31373
  } catch {
31369
31374
  }
31370
31375
  }
31371
- schedule(id2, claims, deadlineAt, run2, signal) {
31376
+ /** lockWaitMs: how long a job may wait behind a conflicting claim before it is refused with
31377
+ * RESOURCE_LOCKED (proved not started), so the caller hears back inside its relay window
31378
+ * instead of timing out (#504). Waiting only for a free slot still lasts until the deadline. */
31379
+ schedule(id2, claims, deadlineAt, run2, signal, lockWaitMs) {
31372
31380
  if (!this.accepting) return Promise.reject(Error("AGENT_PAUSED"));
31373
31381
  if (!id2 || id2.length > 100 || this.active.has(id2) || this.waiting.some((j) => j.id === id2)) return Promise.reject(Error("DUPLICATE_SCHEDULED_EXECUTION"));
31374
31382
  if (!Array.isArray(claims) || !claims.length || claims.length > 32 || claims.some((c) => !c || !path19.isAbsolute(c.path) || !["read", "write"].includes(c.mode))) return Promise.reject(Error("INVALID_RESOURCE_CLAIM"));
@@ -31389,6 +31397,12 @@ var NativeScheduler = class {
31389
31397
  };
31390
31398
  job.aborted = () => remove("CANCELLED_BEFORE_ACCEPTANCE");
31391
31399
  job.timer = setTimeout(() => remove("ADMISSION_DEADLINE_EXPIRED"), Math.min(2147483647, Math.max(1, deadline - Date.now())));
31400
+ if (lockWaitMs !== void 0 && Number.isFinite(lockWaitMs) && lockWaitMs >= 0) {
31401
+ const lockIn = Math.min(lockWaitMs, deadline - Date.now() - 1e3);
31402
+ if (lockIn > 0) job.lockTimer = setTimeout(() => {
31403
+ if (this.lockBlocked(job)) remove("RESOURCE_LOCKED");
31404
+ }, lockIn);
31405
+ }
31392
31406
  signal?.addEventListener("abort", job.aborted, { once: true });
31393
31407
  this.waiting.push(job);
31394
31408
  this.observe({ kind: "queued", executionId: id2, deadlineAt: deadline });
@@ -31397,8 +31411,21 @@ var NativeScheduler = class {
31397
31411
  }
31398
31412
  cleanup(job) {
31399
31413
  clearTimeout(job.timer);
31414
+ clearTimeout(job.lockTimer);
31400
31415
  if (job.aborted) job.signal?.removeEventListener("abort", job.aborted);
31401
31416
  }
31417
+ /** True when a waiting job is held by a conflicting claim: a running job, or an earlier conflicting
31418
+ * waiter that is itself lock-blocked. A wait that is only for a free slot is never a lock. */
31419
+ lockBlocked(job) {
31420
+ const index = this.waiting.indexOf(job);
31421
+ if (index < 0) return false;
31422
+ const blocked2 = [];
31423
+ for (let i = 0; i <= index; i++) {
31424
+ const current2 = this.waiting[i];
31425
+ blocked2[i] = [...this.active.values()].some((claims) => claimsConflict(claims, current2.claims)) || this.waiting.slice(0, i).some((older, j) => blocked2[j] === true && claimsConflict(older.claims, current2.claims));
31426
+ }
31427
+ return blocked2[index] === true;
31428
+ }
31402
31429
  pump() {
31403
31430
  for (let i = 0; i < this.waiting.length && this.active.size < this.safeSlots; ) {
31404
31431
  const job = this.waiting[i];
@@ -32101,6 +32128,7 @@ var knownCodes = /* @__PURE__ */ new Set([
32101
32128
  "WORKSPACE_CHANGED",
32102
32129
  "WORKSPACE_UNAVAILABLE",
32103
32130
  "RESOURCE_BUSY",
32131
+ "RESOURCE_LOCKED",
32104
32132
  "AGENT_PAUSED",
32105
32133
  "CANCELLED_BEFORE_ACCEPTANCE",
32106
32134
  "ADMISSION_DEADLINE_EXPIRED",
@@ -32115,6 +32143,7 @@ var knownCodes = /* @__PURE__ */ new Set([
32115
32143
  "SCREEN_ENCODER_UNAVAILABLE",
32116
32144
  "SCREEN_RECORDING_PERMISSION_REQUIRED"
32117
32145
  ]);
32146
+ var LOCK_WAIT_MS = 2e4;
32118
32147
  function safeCode(error62) {
32119
32148
  const code = error62 instanceof NativeToolError ? error62.code : error62 instanceof Error ? error62.message : "";
32120
32149
  return knownCodes.has(code) || /^GUARD_[A-Z_]{1,30}$/.test(code) ? code : "TOOL_FAILED";
@@ -32326,7 +32355,7 @@ var NativeRuntime = class {
32326
32355
  const final = await this.options.journal.finish(binding2.executionId, image === void 0 ? journalOutput : { ...journalOutput, stdout: screenshotMetadata(image) }, resultCode);
32327
32356
  const outbound = await outboundResult(runnable.call, journalResult(final.result, resultCode), binding2, this.optionalTelemetry, this.options.efficiencyLedger);
32328
32357
  this.send({ type: "result", version: 1, binding: binding2, result: image === void 0 ? outbound : { ...outbound, stdout: image } });
32329
- }, abort.signal);
32358
+ }, abort.signal, LOCK_WAIT_MS);
32330
32359
  context.task = work.catch(async (error62) => {
32331
32360
  if (!context.accepted) {
32332
32361
  if (context.guardPermit) await this.options.guard.rejected(context.guardPermit, safeCode(error62)).catch(() => {
@@ -48979,7 +49008,7 @@ var NativeEventBuffer = class {
48979
49008
 
48980
49009
  // agent/src/native-event-supervisor.ts
48981
49010
  var eventKinds = /* @__PURE__ */ new Set(["queued", "admitted", "released", "queue_rejected", "prepared", "accepted", "running", "result", "not_accepted", "connected", "disconnected", "mailbox_expired"]);
48982
- var codes = /* @__PURE__ */ new Set(["SUCCESS", "EXECUTION_UNKNOWN", "AGENT_RESTARTED", "ADMISSION_DEADLINE_EXPIRED", "CANCELLED_BEFORE_ACCEPTANCE", "RESOURCE_BUSY", "AGENT_PAUSED", "PREPARATION_EXPIRED", "CANCELLED", "TIMEOUT", "USER_PROGRAM_FAILED", "TOOL_FAILED", "MAILBOX_EXPIRED"]);
49011
+ var codes = /* @__PURE__ */ new Set(["SUCCESS", "EXECUTION_UNKNOWN", "AGENT_RESTARTED", "ADMISSION_DEADLINE_EXPIRED", "CANCELLED_BEFORE_ACCEPTANCE", "RESOURCE_BUSY", "RESOURCE_LOCKED", "AGENT_PAUSED", "PREPARATION_EXPIRED", "CANCELLED", "TIMEOUT", "USER_PROGRAM_FAILED", "TOOL_FAILED", "MAILBOX_EXPIRED"]);
48983
49012
  var NativeEventSupervisor = class {
48984
49013
  constructor(onChange) {
48985
49014
  this.onChange = onChange;
@@ -50811,7 +50840,7 @@ async function restoreContentAudit(input2, output2, key) {
50811
50840
  }
50812
50841
 
50813
50842
  // agent/src/main.ts
50814
- var AGENT_VERSION2 = true ? "0.3.47" : "0.3.2";
50843
+ var AGENT_VERSION2 = true ? "0.3.49" : "0.3.2";
50815
50844
  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" : "";
50816
50845
  var AI_CONNECTION_SHIPPED = false;
50817
50846
  function processAlive(pid) {