@amaster.ai/employee-runtime-connector 0.1.1-beta.2 → 0.1.1-beta.21
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 +27 -0
- package/dist/amaster-runtime-daemon.mjs +2575 -541
- package/dist/amaster-runtime.mjs +98 -18
- package/package.json +1 -1
package/dist/amaster-runtime.mjs
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { closeSync, existsSync, mkdirSync, openSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
|
|
3
3
|
import { spawn, spawnSync } from "node:child_process";
|
|
4
|
-
import {
|
|
4
|
+
import { randomUUID } from "node:crypto";
|
|
5
|
+
import { basename, dirname, join, resolve } from "node:path";
|
|
5
6
|
import { homedir, hostname } from "node:os";
|
|
6
7
|
import { fileURLToPath } from "node:url";
|
|
7
8
|
|
|
8
|
-
const CONNECTOR_VERSION = "0.1.1-beta.
|
|
9
|
+
const CONNECTOR_VERSION = "0.1.1-beta.21";
|
|
9
10
|
|
|
10
11
|
const CAPABILITIES = [
|
|
11
12
|
"remote_registration",
|
|
@@ -52,8 +53,8 @@ const CONFIG_KEY_MAP = new Map([
|
|
|
52
53
|
["pi_coding_agent_dir", "PI_CODING_AGENT_DIR"],
|
|
53
54
|
["pi_agent_home", "PI_AGENT_HOME"],
|
|
54
55
|
["pi_agent_node", "PI_AGENT_NODE"],
|
|
55
|
-
["
|
|
56
|
-
["
|
|
56
|
+
["runner_kind", "AMASTER_RUNTIME_RUNNER_KIND"],
|
|
57
|
+
["desktop_platform_system_data_dir", "AMASTER_RUNTIME_DESKTOP_PLATFORM_SYSTEM_DATA_DIR"],
|
|
57
58
|
["heartbeat_log_mode", "AMASTER_HEARTBEAT_LOG_MODE"],
|
|
58
59
|
]);
|
|
59
60
|
|
|
@@ -81,8 +82,8 @@ const ENV_ORDER = [
|
|
|
81
82
|
"PI_CODING_AGENT_DIR",
|
|
82
83
|
"PI_AGENT_HOME",
|
|
83
84
|
"PI_AGENT_NODE",
|
|
84
|
-
"
|
|
85
|
-
"
|
|
85
|
+
"AMASTER_RUNTIME_RUNNER_KIND",
|
|
86
|
+
"AMASTER_RUNTIME_DESKTOP_PLATFORM_SYSTEM_DATA_DIR",
|
|
86
87
|
"AMASTER_HEARTBEAT_LOG_MODE",
|
|
87
88
|
];
|
|
88
89
|
|
|
@@ -364,6 +365,58 @@ function runningStatePid(state, config) {
|
|
|
364
365
|
return pidRunning(pid) ? pid : null;
|
|
365
366
|
}
|
|
366
367
|
|
|
368
|
+
function processCommandLine(pid) {
|
|
369
|
+
try {
|
|
370
|
+
if (process.platform === "linux") {
|
|
371
|
+
return readFileSync(`/proc/${pid}/cmdline`, "utf8").replaceAll("\0", " ").trim() || null;
|
|
372
|
+
}
|
|
373
|
+
if (process.platform === "win32") {
|
|
374
|
+
const result = spawnSync(
|
|
375
|
+
"powershell.exe",
|
|
376
|
+
[
|
|
377
|
+
"-NoProfile",
|
|
378
|
+
"-NonInteractive",
|
|
379
|
+
"-Command",
|
|
380
|
+
`(Get-CimInstance Win32_Process -Filter 'ProcessId = ${pid}').CommandLine`,
|
|
381
|
+
],
|
|
382
|
+
{ encoding: "utf8", windowsHide: true, timeout: 2_000 },
|
|
383
|
+
);
|
|
384
|
+
return result.status === 0 ? result.stdout.trim() || null : null;
|
|
385
|
+
}
|
|
386
|
+
const result = spawnSync("ps", ["-ww", "-p", String(pid), "-o", "command="], {
|
|
387
|
+
encoding: "utf8",
|
|
388
|
+
timeout: 2_000,
|
|
389
|
+
});
|
|
390
|
+
return result.status === 0 ? result.stdout.trim() || null : null;
|
|
391
|
+
} catch {
|
|
392
|
+
return null;
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
function recordedDaemonIdentity(pidInfo, state, config) {
|
|
397
|
+
const pid = Number(pidInfo?.pid);
|
|
398
|
+
if (!pidRunning(pid)) return { status: "not_running", pid };
|
|
399
|
+
const commandLine = processCommandLine(pid);
|
|
400
|
+
// An unavailable OS probe is not proof that the PID belongs to another
|
|
401
|
+
// process. Keep the PID file intact and refuse start/stop so the CLI cannot
|
|
402
|
+
// trade signal safety for a duplicate daemon.
|
|
403
|
+
if (!commandLine) return { status: "unverifiable", pid };
|
|
404
|
+
const processToken = typeof pidInfo?.processToken === "string" ? pidInfo.processToken.trim() : "";
|
|
405
|
+
const expectedScriptPath = resolve(daemonScriptPath());
|
|
406
|
+
if (processToken) {
|
|
407
|
+
// The random token is the primary identity across dev/installed/symlink
|
|
408
|
+
// layouts. The daemon basename and token flag keep an unrelated process
|
|
409
|
+
// that merely contains the token text from matching.
|
|
410
|
+
const matchesTokenIdentity = commandLine.includes("--runtime-process-token")
|
|
411
|
+
&& commandLine.includes(processToken)
|
|
412
|
+
&& (commandLine.includes(expectedScriptPath) || commandLine.includes(basename(expectedScriptPath)));
|
|
413
|
+
return { status: matchesTokenIdentity ? "matched" : "mismatched", pid };
|
|
414
|
+
}
|
|
415
|
+
const matchesLegacyIdentity = commandLine.includes(expectedScriptPath)
|
|
416
|
+
&& runningStatePid(state, config) === pid;
|
|
417
|
+
return { status: matchesLegacyIdentity ? "matched" : "mismatched", pid };
|
|
418
|
+
}
|
|
419
|
+
|
|
367
420
|
function daemonScriptPath() {
|
|
368
421
|
const candidates = [
|
|
369
422
|
join(scriptDir, "amaster-runtime-daemon.mjs"),
|
|
@@ -404,8 +457,8 @@ function printConfig(config) {
|
|
|
404
457
|
["pi_coding_agent_dir", merged.PI_CODING_AGENT_DIR || "(default)"],
|
|
405
458
|
["pi_agent_home", merged.PI_AGENT_HOME || "(default)"],
|
|
406
459
|
["pi_agent_node", merged.PI_AGENT_NODE || "(default)"],
|
|
407
|
-
["
|
|
408
|
-
["
|
|
460
|
+
["runner_kind", merged.AMASTER_RUNTIME_RUNNER_KIND || "cloud_managed"],
|
|
461
|
+
["desktop_platform_system_data_dir", merged.AMASTER_RUNTIME_DESKTOP_PLATFORM_SYSTEM_DATA_DIR ? "(configured)" : "(not configured)"],
|
|
409
462
|
["heartbeat_log_mode", merged.AMASTER_HEARTBEAT_LOG_MODE || "(compact)"],
|
|
410
463
|
["network_domains", merged.AMASTER_NETWORK_DOMAINS],
|
|
411
464
|
["capabilities", merged.AMASTER_CAPABILITIES],
|
|
@@ -875,10 +928,18 @@ function commandDaemon(argv) {
|
|
|
875
928
|
if (action === "restart") commandStop({ quiet: true });
|
|
876
929
|
|
|
877
930
|
const current = readPidInfo();
|
|
878
|
-
|
|
879
|
-
|
|
931
|
+
const currentIdentity = recordedDaemonIdentity(current, readState(config), withDefaults(config));
|
|
932
|
+
if (currentIdentity.status === "unverifiable") {
|
|
933
|
+
throw new Error(
|
|
934
|
+
`Cannot safely start AMaster runtime daemon: pid ${currentIdentity.pid} is live but the process identity probe was unavailable. `
|
|
935
|
+
+ "The PID file was retained; restore the platform process probe or stop the supervised process explicitly before retrying.",
|
|
936
|
+
);
|
|
937
|
+
}
|
|
938
|
+
if (currentIdentity.status === "matched" && flags.force !== "true") {
|
|
939
|
+
process.stdout.write(`AMaster runtime daemon already running pid=${currentIdentity.pid}\n`);
|
|
880
940
|
return;
|
|
881
941
|
}
|
|
942
|
+
if (currentIdentity.status === "matched") commandStop({ quiet: true });
|
|
882
943
|
|
|
883
944
|
if (flags.foreground === "true") {
|
|
884
945
|
spawnDaemonForeground(["start"]);
|
|
@@ -887,7 +948,13 @@ function commandDaemon(argv) {
|
|
|
887
948
|
|
|
888
949
|
mkdirSync(configDir, { recursive: true });
|
|
889
950
|
const fd = openSync(logFile, "a");
|
|
890
|
-
const
|
|
951
|
+
const processToken = randomUUID();
|
|
952
|
+
const child = spawn(process.execPath, [
|
|
953
|
+
daemonScriptPath(),
|
|
954
|
+
"start",
|
|
955
|
+
"--runtime-process-token",
|
|
956
|
+
processToken,
|
|
957
|
+
], {
|
|
891
958
|
cwd: scriptDir,
|
|
892
959
|
detached: true,
|
|
893
960
|
env: {
|
|
@@ -900,6 +967,7 @@ function commandDaemon(argv) {
|
|
|
900
967
|
closeSync(fd);
|
|
901
968
|
writePidInfo({
|
|
902
969
|
pid: child.pid,
|
|
970
|
+
processToken,
|
|
903
971
|
startedAt: new Date().toISOString(),
|
|
904
972
|
serverUrl: withDefaults(config).AMASTER_EMPLOYEE_SERVER_URL,
|
|
905
973
|
logFile,
|
|
@@ -912,12 +980,14 @@ function commandStatus() {
|
|
|
912
980
|
const config = withDefaults(readConfig());
|
|
913
981
|
const pidInfo = readPidInfo();
|
|
914
982
|
const state = readState(config);
|
|
915
|
-
const foregroundPid = runningStatePid(state, config);
|
|
916
|
-
const
|
|
983
|
+
const foregroundPid = pidInfo?.pid ? null : runningStatePid(state, config);
|
|
984
|
+
const recordedIdentity = recordedDaemonIdentity(pidInfo, state, config);
|
|
985
|
+
const runningPid = recordedIdentity.status === "matched" ? recordedIdentity.pid : foregroundPid;
|
|
917
986
|
process.stdout.write("AMaster runtime daemon\n");
|
|
918
|
-
process.stdout.write(`status: ${runningPid ? "running" : "stopped"}\n`);
|
|
987
|
+
process.stdout.write(`status: ${runningPid ? "running" : recordedIdentity.status === "unverifiable" ? "unknown" : "stopped"}\n`);
|
|
919
988
|
if (runningPid) process.stdout.write(`pid: ${runningPid}\n`);
|
|
920
|
-
else if (
|
|
989
|
+
else if (recordedIdentity.status === "unverifiable") process.stdout.write(`unverified_pid: ${recordedIdentity.pid}\n`);
|
|
990
|
+
else if (pidInfo?.pid) process.stdout.write(`stale_pid: ${pidInfo.pid}\n`);
|
|
921
991
|
process.stdout.write(`server_url: ${config.AMASTER_EMPLOYEE_SERVER_URL}\n`);
|
|
922
992
|
process.stdout.write(`device_name: ${config.AMASTER_CONNECTOR_NAME}\n`);
|
|
923
993
|
process.stdout.write(`workspace_bindings: ${config.AMASTER_WORKSPACE_ALLOWLIST}\n`);
|
|
@@ -934,6 +1004,8 @@ function commandStatus() {
|
|
|
934
1004
|
process.stdout.write(`stale_managed_workdirs: ${localSummary.staleManagedWorkdirCount ?? 0}\n`);
|
|
935
1005
|
process.stdout.write(`result_outbox_pending: ${localSummary.resultOutboxPending ?? 0}\n`);
|
|
936
1006
|
process.stdout.write(`result_outbox_invalid: ${localSummary.resultOutboxInvalid ?? 0}\n`);
|
|
1007
|
+
process.stdout.write(`run_completion_pending: ${localSummary.runCompletionPending ?? 0}\n`);
|
|
1008
|
+
process.stdout.write(`run_completion_invalid: ${localSummary.runCompletionInvalid ?? 0}\n`);
|
|
937
1009
|
const recentManagedRuns = Array.isArray(localSummary.recentManagedRuns)
|
|
938
1010
|
? localSummary.recentManagedRuns
|
|
939
1011
|
: [];
|
|
@@ -957,19 +1029,27 @@ function commandStatus() {
|
|
|
957
1029
|
}
|
|
958
1030
|
|
|
959
1031
|
function commandStop(opts = {}) {
|
|
1032
|
+
const config = withDefaults(readConfig());
|
|
960
1033
|
const pidInfo = readPidInfo();
|
|
961
|
-
|
|
1034
|
+
const recordedIdentity = recordedDaemonIdentity(pidInfo, readState(config), config);
|
|
1035
|
+
if (recordedIdentity.status === "unverifiable") {
|
|
1036
|
+
throw new Error(
|
|
1037
|
+
`Cannot safely stop AMaster runtime daemon: pid ${recordedIdentity.pid} is live but the process identity probe was unavailable. `
|
|
1038
|
+
+ "The PID file was retained and no signal was sent.",
|
|
1039
|
+
);
|
|
1040
|
+
}
|
|
1041
|
+
if (recordedIdentity.status !== "matched") {
|
|
962
1042
|
if (!opts.quiet) process.stdout.write("AMaster runtime daemon is not running\n");
|
|
963
1043
|
rmSync(pidFile, { force: true });
|
|
964
1044
|
return;
|
|
965
1045
|
}
|
|
966
1046
|
try {
|
|
967
|
-
process.kill(
|
|
1047
|
+
process.kill(recordedIdentity.pid, "SIGTERM");
|
|
968
1048
|
} catch {
|
|
969
1049
|
// Process may exit between the status check and SIGTERM.
|
|
970
1050
|
}
|
|
971
1051
|
rmSync(pidFile, { force: true });
|
|
972
|
-
if (!opts.quiet) process.stdout.write(`AMaster runtime daemon stopped pid=${
|
|
1052
|
+
if (!opts.quiet) process.stdout.write(`AMaster runtime daemon stopped pid=${recordedIdentity.pid}\n`);
|
|
973
1053
|
}
|
|
974
1054
|
|
|
975
1055
|
function passthroughDaemon(command, argv = []) {
|