@demicodes/host-local 0.17.3 → 0.18.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/index.d.mts +16 -2
- package/dist/index.mjs +113 -21
- package/package.json +5 -5
package/dist/index.d.mts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { AgentHarness, AgentServer, AgentServerSessionOptions } from "@demicodes/agent";
|
|
2
2
|
import { resolveDemiHome } from "@demicodes/provider/credentials-pool";
|
|
3
|
-
import { BashEnvironmentOptions, Host, HostFileSystem, HostProcess, HostStore } from "@demicodes/shell";
|
|
3
|
+
import { BashEnvironmentOptions, Host, HostFileSystem, HostIdentity, HostProcess, HostStore } from "@demicodes/shell";
|
|
4
4
|
import { Provider } from "@demicodes/provider";
|
|
5
5
|
//#region src/command-bridge.d.ts
|
|
6
6
|
/**
|
|
@@ -50,6 +50,8 @@ interface LocalHostOptions {
|
|
|
50
50
|
storeRoot?: string;
|
|
51
51
|
/** Where command artifacts land as plain files; defaults next to the store. */
|
|
52
52
|
commandArtifactsDir?: string;
|
|
53
|
+
/** Bring-your-own persistence, e.g. to wrap or gate writes; defaults to a LocalHostStore rooted at storeRoot. */
|
|
54
|
+
store?: HostStore;
|
|
53
55
|
}
|
|
54
56
|
declare class LocalHost implements Host {
|
|
55
57
|
readonly defaultCwd: string;
|
|
@@ -57,9 +59,21 @@ declare class LocalHost implements Host {
|
|
|
57
59
|
readonly fs: HostFileSystem;
|
|
58
60
|
readonly process: HostProcess;
|
|
59
61
|
readonly store: HostStore;
|
|
62
|
+
readonly identity: HostIdentity;
|
|
60
63
|
constructor(defaultCwd: string, options?: LocalHostOptions);
|
|
61
64
|
}
|
|
62
65
|
//#endregion
|
|
66
|
+
//#region src/local-store.d.ts
|
|
67
|
+
declare class LocalHostStore implements HostStore {
|
|
68
|
+
readonly root: string;
|
|
69
|
+
constructor(root: string);
|
|
70
|
+
readJson<T>(key: string): Promise<T | null>;
|
|
71
|
+
writeJson<T>(key: string, value: T): Promise<void>;
|
|
72
|
+
delete(key: string): Promise<void>;
|
|
73
|
+
list(prefix: string): Promise<string[]>;
|
|
74
|
+
private pathForKey;
|
|
75
|
+
}
|
|
76
|
+
//#endregion
|
|
63
77
|
//#region src/local-agent-server.d.ts
|
|
64
78
|
interface CreateLocalAgentServerOptions {
|
|
65
79
|
/** Same LocalHost instance the harness was built with. */
|
|
@@ -106,4 +120,4 @@ interface LocalAgentServerHandle {
|
|
|
106
120
|
*/
|
|
107
121
|
declare function createLocalAgentServer(options: CreateLocalAgentServerOptions): LocalAgentServerHandle;
|
|
108
122
|
//#endregion
|
|
109
|
-
export { COMMAND_BRIDGE_SHIM_SOURCE, CommandBridgeHandle, CommandBridgeOptions, CreateLocalAgentServerOptions, LocalAgentServerHandle, LocalHost, LocalHostOptions, MaterializeCommandBridgeShimsOptions, bridgeBinDirFor, createLocalAgentServer, defaultBridgeSocketPath, materializeCommandBridgeShims, resolveDemiHome, startCommandBridge };
|
|
123
|
+
export { COMMAND_BRIDGE_SHIM_SOURCE, CommandBridgeHandle, CommandBridgeOptions, CreateLocalAgentServerOptions, LocalAgentServerHandle, LocalHost, LocalHostOptions, LocalHostStore, MaterializeCommandBridgeShimsOptions, bridgeBinDirFor, createLocalAgentServer, defaultBridgeSocketPath, materializeCommandBridgeShims, resolveDemiHome, startCommandBridge };
|
package/dist/index.mjs
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import { existsSync, mkdirSync, unlinkSync } from "node:fs";
|
|
2
|
-
import { dirname, isAbsolute, join, relative, resolve } from "node:path";
|
|
1
|
+
import { constants, existsSync, mkdirSync, unlinkSync } from "node:fs";
|
|
2
|
+
import { dirname, isAbsolute, join, posix, relative, resolve } from "node:path";
|
|
3
3
|
import { createServer } from "node:http";
|
|
4
4
|
import { AgentServer, RunCommandLineCommandNotRegisteredError, RunCommandLineShellNotFoundError, RunCommandLineTimeoutError } from "@demicodes/agent";
|
|
5
5
|
import { encodeUtf8, errorMessage, isFileNotFoundError, parsePortableJson, stringifyPortableJson } from "@demicodes/utils";
|
|
6
6
|
import { createHash, randomUUID } from "node:crypto";
|
|
7
7
|
import { resolveDemiHome } from "@demicodes/provider/credentials-pool";
|
|
8
8
|
import { spawn } from "node:child_process";
|
|
9
|
-
import { homedir } from "node:os";
|
|
10
|
-
import { appendFile, chmod, cp, link, lstat, mkdir, readFile, readdir, readlink, realpath, rename, rm, stat, symlink, utimes, writeFile } from "node:fs/promises";
|
|
9
|
+
import { homedir, hostname, userInfo } from "node:os";
|
|
10
|
+
import { appendFile, chmod, cp, link, lstat, mkdir, open, readFile, readdir, readlink, realpath, rename, rm, stat, symlink, utimes, writeFile } from "node:fs/promises";
|
|
11
11
|
//#region src/command-bridge.ts
|
|
12
12
|
/**
|
|
13
13
|
* Dispatch script shared by every generated command-name symlink.
|
|
@@ -337,6 +337,61 @@ function validateHostStoreKey(key) {
|
|
|
337
337
|
for (const segment of key.split(/[\\/]+/)) if (segment === "..") throw new Error(`HostStore keys must not contain path traversal: ${key}`);
|
|
338
338
|
}
|
|
339
339
|
//#endregion
|
|
340
|
+
//#region src/local-cwd.ts
|
|
341
|
+
const DIR_FLAGS = constants.O_RDONLY | constants.O_DIRECTORY;
|
|
342
|
+
var LocalHostCwd = class LocalHostCwd {
|
|
343
|
+
path;
|
|
344
|
+
handle;
|
|
345
|
+
static async open(path) {
|
|
346
|
+
const cwd = new LocalHostCwd(path);
|
|
347
|
+
cwd.handle = await open(path, DIR_FLAGS);
|
|
348
|
+
return cwd;
|
|
349
|
+
}
|
|
350
|
+
constructor(path) {
|
|
351
|
+
this.path = path;
|
|
352
|
+
}
|
|
353
|
+
spawnPath() {
|
|
354
|
+
return this.fdAnchor() ?? this.path;
|
|
355
|
+
}
|
|
356
|
+
async chdir(path) {
|
|
357
|
+
if (path === ".") return;
|
|
358
|
+
const target = isAbsolute(path) ? path : this.fdAnchor() ? `${this.fdAnchor()}/${path}` : posix.join(this.path, path);
|
|
359
|
+
const next = await open(target, DIR_FLAGS);
|
|
360
|
+
const previous = this.handle;
|
|
361
|
+
this.handle = next;
|
|
362
|
+
this.path = isAbsolute(path) ? path : posix.normalize(posix.join(this.path, path));
|
|
363
|
+
await previous?.close().catch(() => {});
|
|
364
|
+
}
|
|
365
|
+
async snapshot() {
|
|
366
|
+
const anchor = this.fdAnchor();
|
|
367
|
+
if (!this.handle || !anchor) {
|
|
368
|
+
const path = this.path;
|
|
369
|
+
const handle = this.handle;
|
|
370
|
+
return { restore: () => {
|
|
371
|
+
this.path = path;
|
|
372
|
+
this.handle = handle;
|
|
373
|
+
} };
|
|
374
|
+
}
|
|
375
|
+
const dup = await open(anchor, DIR_FLAGS);
|
|
376
|
+
const path = this.path;
|
|
377
|
+
return { restore: () => {
|
|
378
|
+
const abandoned = this.handle;
|
|
379
|
+
this.handle = dup;
|
|
380
|
+
this.path = path;
|
|
381
|
+
if (abandoned && abandoned.fd !== dup.fd) abandoned.close().catch(() => {});
|
|
382
|
+
} };
|
|
383
|
+
}
|
|
384
|
+
async close() {
|
|
385
|
+
await this.handle?.close().catch(() => {});
|
|
386
|
+
this.handle = void 0;
|
|
387
|
+
}
|
|
388
|
+
fdAnchor() {
|
|
389
|
+
if (!this.handle) return void 0;
|
|
390
|
+
if (process.platform === "linux") return `/proc/self/fd/${this.handle.fd}`;
|
|
391
|
+
if (process.platform === "darwin") return `/dev/fd/${this.handle.fd}`;
|
|
392
|
+
}
|
|
393
|
+
};
|
|
394
|
+
//#endregion
|
|
340
395
|
//#region src/local-host.ts
|
|
341
396
|
var LocalHost = class {
|
|
342
397
|
defaultCwd;
|
|
@@ -344,13 +399,20 @@ var LocalHost = class {
|
|
|
344
399
|
fs;
|
|
345
400
|
process;
|
|
346
401
|
store;
|
|
402
|
+
identity;
|
|
347
403
|
constructor(defaultCwd, options = {}) {
|
|
348
404
|
this.defaultCwd = resolve(defaultCwd);
|
|
349
405
|
const storeRoot = options.storeRoot ?? defaultStoreRoot(this.defaultCwd);
|
|
350
406
|
this.commandArtifactsDir = resolve(options.commandArtifactsDir ?? join(storeRoot, "command-artifacts"));
|
|
351
407
|
this.fs = new LocalHostFileSystem(this.defaultCwd);
|
|
352
408
|
this.process = new LocalHostProcess(this.defaultCwd);
|
|
353
|
-
this.store = new LocalHostStore(storeRoot);
|
|
409
|
+
this.store = options.store ?? new LocalHostStore(storeRoot);
|
|
410
|
+
const info = userInfo();
|
|
411
|
+
this.identity = {
|
|
412
|
+
uid: info.uid,
|
|
413
|
+
gid: info.gid,
|
|
414
|
+
hostname: hostname()
|
|
415
|
+
};
|
|
354
416
|
}
|
|
355
417
|
};
|
|
356
418
|
var LocalHostProcess = class {
|
|
@@ -358,13 +420,14 @@ var LocalHostProcess = class {
|
|
|
358
420
|
constructor(defaultCwd) {
|
|
359
421
|
this.defaultCwd = defaultCwd;
|
|
360
422
|
}
|
|
423
|
+
async openCwd(path) {
|
|
424
|
+
return LocalHostCwd.open(path);
|
|
425
|
+
}
|
|
361
426
|
async spawn(params) {
|
|
427
|
+
const cwd = params.cwd ?? this.defaultCwd;
|
|
362
428
|
const child = spawn(params.command, params.args ?? [], {
|
|
363
|
-
cwd
|
|
364
|
-
env: {
|
|
365
|
-
...process.env,
|
|
366
|
-
...params.env
|
|
367
|
-
},
|
|
429
|
+
cwd,
|
|
430
|
+
...params.env ? { env: definedEnv(params.env) } : {},
|
|
368
431
|
detached: params.killProcessGroup === true,
|
|
369
432
|
stdio: [
|
|
370
433
|
"pipe",
|
|
@@ -373,21 +436,26 @@ var LocalHostProcess = class {
|
|
|
373
436
|
]
|
|
374
437
|
});
|
|
375
438
|
let settled = false;
|
|
376
|
-
const waitPromise = new Promise((
|
|
439
|
+
const waitPromise = new Promise((resolveWait) => {
|
|
377
440
|
child.once("error", (error) => {
|
|
378
441
|
if (settled) return;
|
|
379
442
|
settled = true;
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
443
|
+
classifySpawnFailure(error, cwd).then((kind) => {
|
|
444
|
+
resolveWait({
|
|
445
|
+
exitCode: null,
|
|
446
|
+
signal: error.message,
|
|
447
|
+
spawnError: { kind }
|
|
448
|
+
});
|
|
383
449
|
});
|
|
384
450
|
});
|
|
385
451
|
child.once("close", (exitCode, signal) => {
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
452
|
+
setImmediate(() => {
|
|
453
|
+
if (settled) return;
|
|
454
|
+
settled = true;
|
|
455
|
+
resolveWait({
|
|
456
|
+
exitCode,
|
|
457
|
+
signal: signal ?? void 0
|
|
458
|
+
});
|
|
391
459
|
});
|
|
392
460
|
});
|
|
393
461
|
});
|
|
@@ -565,9 +633,33 @@ function toHostFileStat(value) {
|
|
|
565
633
|
isSymbolicLink: value.isSymbolicLink(),
|
|
566
634
|
mode: value.mode,
|
|
567
635
|
size: value.size,
|
|
568
|
-
mtime: value.mtime
|
|
636
|
+
mtime: value.mtime,
|
|
637
|
+
uid: value.uid,
|
|
638
|
+
gid: value.gid,
|
|
639
|
+
ino: value.ino,
|
|
640
|
+
dev: value.dev,
|
|
641
|
+
nlink: value.nlink,
|
|
642
|
+
isCharacterDevice: value.isCharacterDevice(),
|
|
643
|
+
isFIFO: value.isFIFO()
|
|
569
644
|
};
|
|
570
645
|
}
|
|
646
|
+
function definedEnv(env) {
|
|
647
|
+
const defined = {};
|
|
648
|
+
for (const [key, value] of Object.entries(env)) if (value !== void 0) defined[key] = value;
|
|
649
|
+
return defined;
|
|
650
|
+
}
|
|
651
|
+
async function classifySpawnFailure(error, cwd) {
|
|
652
|
+
try {
|
|
653
|
+
if (!(await stat(cwd)).isDirectory()) return "cwd_unusable";
|
|
654
|
+
} catch {
|
|
655
|
+
return "cwd_unusable";
|
|
656
|
+
}
|
|
657
|
+
const code = "code" in error ? String(error.code) : "";
|
|
658
|
+
if (code === "ENOENT") return "executable_not_found";
|
|
659
|
+
if (code === "EACCES" || code === "EPERM") return "permission_denied";
|
|
660
|
+
if (code === "EISDIR") return "is_directory";
|
|
661
|
+
return "other";
|
|
662
|
+
}
|
|
571
663
|
function toHostDirent(value) {
|
|
572
664
|
return {
|
|
573
665
|
name: value.name,
|
|
@@ -657,4 +749,4 @@ function createLocalAgentServer(options) {
|
|
|
657
749
|
};
|
|
658
750
|
}
|
|
659
751
|
//#endregion
|
|
660
|
-
export { COMMAND_BRIDGE_SHIM_SOURCE, LocalHost, bridgeBinDirFor, createLocalAgentServer, defaultBridgeSocketPath, materializeCommandBridgeShims, resolveDemiHome, startCommandBridge };
|
|
752
|
+
export { COMMAND_BRIDGE_SHIM_SOURCE, LocalHost, LocalHostStore, bridgeBinDirFor, createLocalAgentServer, defaultBridgeSocketPath, materializeCommandBridgeShims, resolveDemiHome, startCommandBridge };
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@demicodes/host-local",
|
|
3
3
|
"description": "Node LocalHost and open-box local AgentServer assembly (command bridge on by default).",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.18.0",
|
|
5
5
|
"private": false,
|
|
6
6
|
"type": "module",
|
|
7
7
|
"exports": {
|
|
@@ -11,10 +11,10 @@
|
|
|
11
11
|
}
|
|
12
12
|
},
|
|
13
13
|
"dependencies": {
|
|
14
|
-
"@demicodes/agent": "^0.
|
|
15
|
-
"@demicodes/provider": "^0.
|
|
16
|
-
"@demicodes/shell": "^0.
|
|
17
|
-
"@demicodes/utils": "^0.
|
|
14
|
+
"@demicodes/agent": "^0.18.0",
|
|
15
|
+
"@demicodes/provider": "^0.18.0",
|
|
16
|
+
"@demicodes/shell": "^0.18.0",
|
|
17
|
+
"@demicodes/utils": "^0.18.0"
|
|
18
18
|
},
|
|
19
19
|
"license": "Apache-2.0",
|
|
20
20
|
"main": "./dist/index.mjs",
|