@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.
@@ -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 { dirname, join, resolve } from "node:path";
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.2";
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
- ["pi_agent_runner_mode", "AMASTER_PI_AGENT_RUNNER_MODE"],
56
- ["pi_agent_system_data_dir", "AMASTER_PI_AGENT_SYSTEM_DATA_DIR"],
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
- "AMASTER_PI_AGENT_RUNNER_MODE",
85
- "AMASTER_PI_AGENT_SYSTEM_DATA_DIR",
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
- ["pi_agent_runner_mode", merged.AMASTER_PI_AGENT_RUNNER_MODE || "(disabled)"],
408
- ["pi_agent_system_data_dir", merged.AMASTER_PI_AGENT_SYSTEM_DATA_DIR ? "(configured)" : "(not configured)"],
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
- if (current?.pid && pidRunning(current.pid) && flags.force !== "true") {
879
- process.stdout.write(`AMaster runtime daemon already running pid=${current.pid}\n`);
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 child = spawn(process.execPath, [daemonScriptPath(), "start"], {
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 runningPid = pidInfo?.pid && pidRunning(pidInfo.pid) ? pidInfo.pid : foregroundPid;
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 (pidInfo?.pid) process.stdout.write(`pid: ${pidInfo.pid}\n`);
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
- if (!pidInfo?.pid || !pidRunning(pidInfo.pid)) {
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(pidInfo.pid, "SIGTERM");
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=${pidInfo.pid}\n`);
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 = []) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@amaster.ai/employee-runtime-connector",
3
- "version": "0.1.1-beta.2",
3
+ "version": "0.1.1-beta.21",
4
4
  "description": "MirrorX runtime connector CLI and daemon",
5
5
  "license": "MIT",
6
6
  "type": "module",