@amaster.ai/employee-runtime-connector 0.1.1-beta.7 → 0.1.1-beta.8

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.
@@ -7277,6 +7277,14 @@ function runtimeStatusOutboxDir(config) {
7277
7277
  AMASTER_DAEMON_STATE_FILE: statePath
7278
7278
  })), "result-outbox");
7279
7279
  }
7280
+ function runtimeStatusRunCompletionDir(config) {
7281
+ const explicit = config.AMASTER_RUN_COMPLETION_STATE_DIR ?? config.runCompletionStateDir;
7282
+ if (typeof explicit === "string" && explicit.trim()) return resolve6(explicit);
7283
+ const statePath = config.AMASTER_DAEMON_STATE_FILE ?? config.daemonStateFile;
7284
+ return join10(dirname6(stateFilePath({
7285
+ AMASTER_DAEMON_STATE_FILE: statePath
7286
+ })), "run-completion-state");
7287
+ }
7280
7288
  function countRuntimeStatusJsonEntries(dir) {
7281
7289
  if (!existsSync9(dir)) return 0;
7282
7290
  return runtimeStatusDirectoryEntries(dir).filter((entry) => entry.isFile() && entry.name.endsWith(".json")).length;
@@ -7311,6 +7319,8 @@ function summarizeRuntimeLocalState(input) {
7311
7319
  }
7312
7320
  const pendingOutboxDir = runtimeStatusOutboxDir(config);
7313
7321
  const invalidOutboxDir = join10(pendingOutboxDir, "invalid");
7322
+ const pendingRunCompletionDir = runtimeStatusRunCompletionDir(config);
7323
+ const invalidRunCompletionDir = join10(pendingRunCompletionDir, "invalid");
7314
7324
  return {
7315
7325
  managedWorkspacesRoot: root,
7316
7326
  managedWorkdirCount: workdirs.length,
@@ -7320,7 +7330,9 @@ function summarizeRuntimeLocalState(input) {
7320
7330
  newestTouchedAt: newestTouchedTime === null ? null : new Date(newestTouchedTime).toISOString(),
7321
7331
  recentManagedRuns: recentManagedRuns.sort((left, right) => right.sortTime - left.sortTime || left.manifestPath.localeCompare(right.manifestPath)).slice(0, input.recentManagedRunLimit ?? 5).map(({ sortTime: _sortTime, ...entry }) => entry),
7322
7332
  resultOutboxPending: countRuntimeStatusJsonEntries(pendingOutboxDir),
7323
- resultOutboxInvalid: countRuntimeStatusJsonEntries(invalidOutboxDir)
7333
+ resultOutboxInvalid: countRuntimeStatusJsonEntries(invalidOutboxDir),
7334
+ runCompletionPending: countRuntimeStatusJsonEntries(pendingRunCompletionDir),
7335
+ runCompletionInvalid: countRuntimeStatusJsonEntries(invalidRunCompletionDir)
7324
7336
  };
7325
7337
  }
7326
7338
 
@@ -9197,7 +9209,7 @@ function assertSourceAcquisitionRuntimeAuthority({
9197
9209
  }
9198
9210
 
9199
9211
  // src/amaster-runtime-daemon.mjs
9200
- var CONNECTOR_VERSION = "0.1.1-beta.7";
9212
+ var CONNECTOR_VERSION = "0.1.1-beta.8";
9201
9213
  var CONNECTOR_CONTRACT_VERSION = "2026-06-04.v1";
9202
9214
  var SOURCE_ACQUISITION_CAPABILITY = "source_acquisition_v1";
9203
9215
  var SOURCE_ACQUISITION_PROFILE_VERSION = "source_acquisition_v1";
@@ -9433,6 +9445,9 @@ function resultOutboxFailedRunCommands(config) {
9433
9445
  }
9434
9446
  return entries;
9435
9447
  }
9448
+ function activeRunCommandCountsAsActive(entry) {
9449
+ return readString(entry?.phase) !== "result_outbox_failed" && readString(entry?.evidenceDeliveryStatus) !== "failed";
9450
+ }
9436
9451
  function clearDeliveredResultOutboxCommand(config, entry) {
9437
9452
  const commandId = readString(entry?.commandId);
9438
9453
  if (commandId) pendingResultOutboxRunCommands.delete(commandId);
@@ -9860,6 +9875,7 @@ function buildHeartbeatPayload(config, options = {}) {
9860
9875
  activeRunCommandById.set(commandId ?? `pending-${activeRunCommandById.size}`, entry);
9861
9876
  }
9862
9877
  const activeRunCommandList = Array.from(activeRunCommandById.values()).map((entry) => buildActiveRunCommandStatus(config, entry));
9878
+ const activeRunCount = activeRunCommandList.filter(activeRunCommandCountsAsActive).length;
9863
9879
  const executorReadiness = buildExecutorReadiness(config);
9864
9880
  const platformCredential = piAgentLocalPlatformCredentialStatus(config);
9865
9881
  const headedBrowserAuth = browserCapabilityStatus(config, "headed_browser_auth");
@@ -9882,7 +9898,7 @@ function buildHeartbeatPayload(config, options = {}) {
9882
9898
  ...heartbeatMode === "diagnostic" ? { heartbeatMode: "diagnostic" } : {},
9883
9899
  ...diagnosticSource ? { diagnosticSource } : {},
9884
9900
  ...orphanCheck === "startup" ? { orphanCheck: "startup" } : {},
9885
- activeRuns: activeRunCommandList.length,
9901
+ activeRuns: activeRunCount,
9886
9902
  ...activeRunCommandList.length > 0 ? { activeRunCommands: activeRunCommandList } : {},
9887
9903
  pollIntervalSeconds: config.pollIntervalSeconds,
9888
9904
  orphanReaperIntervalSeconds: config.orphanReaperIntervalSeconds,
@@ -14594,7 +14610,8 @@ function statusSummary(config) {
14594
14610
  process.stdout.write(`${JSON.stringify(summarizeRuntimeLocalState({
14595
14611
  config: {
14596
14612
  ...config,
14597
- AMASTER_DAEMON_STATE_FILE: stateFilePath(process.env)
14613
+ AMASTER_DAEMON_STATE_FILE: stateFilePath(process.env),
14614
+ AMASTER_RUN_COMPLETION_STATE_DIR: process.env.AMASTER_RUN_COMPLETION_STATE_DIR
14598
14615
  }
14599
14616
  }), null, 2)}
14600
14617
  `);
@@ -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.7";
9
+ const CONNECTOR_VERSION = "0.1.1-beta.8";
9
10
 
10
11
  const CAPABILITIES = [
11
12
  "remote_registration",
@@ -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"),
@@ -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.7",
3
+ "version": "0.1.1-beta.8",
4
4
  "description": "MirrorX runtime connector CLI and daemon",
5
5
  "license": "MIT",
6
6
  "type": "module",