@amaster.ai/employee-runtime-connector 0.1.0-beta.22 → 0.1.0-beta.24

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 CHANGED
@@ -38,7 +38,7 @@ The daemon reports its connector contract, exact package/build, platform/archite
38
38
 
39
39
  `AMASTER_RUNTIME_ATTESTATION_MODE` defaults to `shadow`. Set it to `enforce` only after the expected version and build marker are configured and shadow diagnostics are clean; enforce mode refuses to create or lease Runtime V2 mutation commands without a current exact attestation. `AMASTER_RUNTIME_ATTESTATION_TTL_SECONDS` defaults to `300` and accepts `60`–`3600`. Invalid mode or TTL values fail server startup visibly.
40
40
 
41
- Threat model: these facts are self-reported by a connector authenticated with its existing connector credential. There is no hardware-backed signature, remote attestation, or independent trust root. The proof detects accidental version/schema/executor drift by an honest connector and correlates commands to the observed facts; it does not prove that a compromised or malicious connector is running the claimed code. Keep the default `shadow` mode observational. Do not treat this mechanism as a production security boundary or enable `enforce` until credential-epoch identity, transactional create/lease revalidation, batch-local rejection, and recovery from honest drift have passed review.
41
+ Threat model: these facts are self-reported by a connector authenticated with its existing connector credential. There is no hardware-backed signature, remote attestation, or independent trust root. The proof detects accidental version/schema/executor drift by an honest connector and correlates commands to the observed facts; it does not prove that a compromised or malicious connector is running the claimed code. Proof identity is stable for the exact connector credential and fact set, create/lease revalidate it under the connector transaction lock, stale commands are isolated within a poll batch, and an active connector can recover after restoring exact facts. Keep the default `shadow` mode observational, and do not treat this mechanism as a standalone production security boundary or enable `enforce` before expected markers are configured and shadow diagnostics are clean.
42
42
 
43
43
  The AMaster remote stack derives its runtime image from the Pi base image with
44
44
  `docker/Dockerfile.amaster-employee-pi-cli-runtime`. The build installs one exact
@@ -2144,6 +2144,27 @@ function selectExecutor(config, command) {
2144
2144
  function processGroupIdForChild(child, processPlatform = process.platform) {
2145
2145
  return processPlatform === "win32" || typeof child.pid !== "number" || child.pid <= 0 ? null : child.pid;
2146
2146
  }
2147
+ function isExecutorProcessAlive(child, processGroupId = processGroupIdForChild(child), processApi = process) {
2148
+ if (processGroupId !== null) {
2149
+ try {
2150
+ processApi.kill(-processGroupId, 0);
2151
+ return true;
2152
+ } catch (error) {
2153
+ if (error?.code === "ESRCH") return false;
2154
+ return true;
2155
+ }
2156
+ }
2157
+ return child.exitCode === null && child.signalCode === null;
2158
+ }
2159
+ function isProcessIdAlive(pid, processApi = process) {
2160
+ if (!Number.isInteger(pid) || pid <= 0) return false;
2161
+ try {
2162
+ processApi.kill(pid, 0);
2163
+ return true;
2164
+ } catch (error) {
2165
+ return error?.code !== "ESRCH";
2166
+ }
2167
+ }
2147
2168
  function signalExecutorProcess(child, signal, processGroupId = processGroupIdForChild(child), processApi = process) {
2148
2169
  if (processGroupId !== null) {
2149
2170
  try {
@@ -2156,7 +2177,7 @@ function signalExecutorProcess(child, signal, processGroupId = processGroupIdFor
2156
2177
  }
2157
2178
  }
2158
2179
  }
2159
- if (!child.killed) {
2180
+ if (child.exitCode === null && child.signalCode === null) {
2160
2181
  try {
2161
2182
  return child.kill(signal);
2162
2183
  } catch {
@@ -3759,14 +3780,15 @@ function buildRuntimeConnectorAuthHeaders(config) {
3759
3780
  if (config.token) return { Authorization: `Bearer ${config.token}` };
3760
3781
  return {};
3761
3782
  }
3762
- async function postRuntimeConnectorJson(config, path, payload) {
3783
+ async function postRuntimeConnectorJson(config, path, payload, options = {}) {
3763
3784
  const res = await fetch(`${config.serverUrl}${path}`, {
3764
3785
  method: "POST",
3765
3786
  headers: {
3766
3787
  "content-type": "application/json",
3767
3788
  ...buildRuntimeConnectorAuthHeaders(config)
3768
3789
  },
3769
- body: JSON.stringify(payload)
3790
+ body: JSON.stringify(payload),
3791
+ signal: options.signal
3770
3792
  });
3771
3793
  const text = await res.text();
3772
3794
  const body = text ? JSON.parse(text) : null;
@@ -3797,6 +3819,26 @@ async function postRuntimeConnectorBytes(config, path, body, headers = {}) {
3797
3819
  }
3798
3820
  return response;
3799
3821
  }
3822
+ function retryableRuntimeConnectorPost(error) {
3823
+ const status = Number(error?.httpStatus);
3824
+ return !Number.isInteger(status) || status === 408 || status === 429 || status >= 500;
3825
+ }
3826
+ async function postRuntimeConnectorJsonWithRetry(config, path, payload, options = {}) {
3827
+ const maxAttempts = Math.max(1, Math.min(5, Number(options.maxAttempts ?? 3)));
3828
+ const timeoutMs = Math.max(1, Number(options.timeoutMs ?? 5e3));
3829
+ const delayMs = Math.max(0, Number(options.delayMs ?? 100));
3830
+ let lastError;
3831
+ for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
3832
+ try {
3833
+ return await postRuntimeConnectorJson(config, path, payload, { signal: AbortSignal.timeout(timeoutMs) });
3834
+ } catch (error) {
3835
+ lastError = error;
3836
+ if (attempt >= maxAttempts || !retryableRuntimeConnectorPost(error)) throw error;
3837
+ if (delayMs > 0) await new Promise((resolve9) => setTimeout(resolve9, delayMs));
3838
+ }
3839
+ }
3840
+ throw lastError;
3841
+ }
3800
3842
  async function bestEffortPostRuntimeConnectorJson(config, path, payload, options = {}) {
3801
3843
  try {
3802
3844
  return await postRuntimeConnectorJson(config, path, payload);
@@ -3812,6 +3854,7 @@ async function bestEffortPostRuntimeConnectorJson(config, path, payload, options
3812
3854
  }
3813
3855
  }
3814
3856
  var postJson = postRuntimeConnectorJson;
3857
+ var postJsonWithRetry = postRuntimeConnectorJsonWithRetry;
3815
3858
  var bestEffortPostJson = bestEffortPostRuntimeConnectorJson;
3816
3859
 
3817
3860
  // src/amaster-runtime-daemon/runtime-artifact-upload.mjs
@@ -3874,6 +3917,38 @@ function prepareRuntimeArtifactUploads(cwd, mcpToolResults) {
3874
3917
  return [...uploads.values()];
3875
3918
  }
3876
3919
 
3920
+ // src/amaster-runtime-daemon/runtime-artifact-ingest-queue.mjs
3921
+ function createRuntimeArtifactIngestQueue({ ingest, onError }) {
3922
+ const handledIntentIds = /* @__PURE__ */ new Set();
3923
+ const artifacts = [];
3924
+ let error = null;
3925
+ let queue = Promise.resolve();
3926
+ return {
3927
+ enqueue(results) {
3928
+ if (error) return;
3929
+ const pending = results.filter((result2) => {
3930
+ const intentId = readString(asRecord(result2).artifactIntent?.intentId);
3931
+ if (!intentId || handledIntentIds.has(intentId)) return false;
3932
+ handledIntentIds.add(intentId);
3933
+ return true;
3934
+ });
3935
+ if (pending.length === 0) return;
3936
+ queue = queue.then(async () => artifacts.push(...await ingest(pending))).catch((caught) => {
3937
+ error ??= caught;
3938
+ onError?.(caught);
3939
+ });
3940
+ },
3941
+ hasHandled(intentId) {
3942
+ return handledIntentIds.has(intentId);
3943
+ },
3944
+ async flush() {
3945
+ await queue;
3946
+ if (error) throw error;
3947
+ return [...artifacts];
3948
+ }
3949
+ };
3950
+ }
3951
+
3877
3952
  // src/amaster-runtime-daemon/workspace-guard.mjs
3878
3953
  import { createHash as createHash4 } from "node:crypto";
3879
3954
  import { existsSync as existsSync5, mkdirSync as mkdirSync4, realpathSync as realpathSync2, statSync as statSync3 } from "node:fs";
@@ -3928,6 +4003,7 @@ function createWorkspaceManifest(workspace, input = {}) {
3928
4003
  executorKind: input.executorKind ?? null,
3929
4004
  executorHome: workspace.executorHome ?? null,
3930
4005
  commandId: input.commandId ?? null,
4006
+ generation: input.generation ?? null,
3931
4007
  runId: input.runId ?? null,
3932
4008
  issueId: input.issueId ?? null,
3933
4009
  workspaceKey: workspace.workspaceKey ?? null,
@@ -4041,6 +4117,7 @@ function resolveExecutionWorkspace(config, command, opts = {}) {
4041
4117
  createWorkspaceManifest(workspace, {
4042
4118
  executorKind,
4043
4119
  commandId: readString(command.commandId) ?? null,
4120
+ generation: Number.isInteger(command.generation) ? command.generation : null,
4044
4121
  runId,
4045
4122
  issueId
4046
4123
  });
@@ -4618,7 +4695,7 @@ function readWorkspaceStatus(cwd, opts = {}) {
4618
4695
  }
4619
4696
 
4620
4697
  // src/amaster-runtime-daemon.mjs
4621
- var CONNECTOR_VERSION = "0.1.0-beta.22";
4698
+ var CONNECTOR_VERSION = "0.1.0-beta.24";
4622
4699
  var CONNECTOR_CONTRACT_VERSION = "2026-06-04.v1";
4623
4700
  var MAX_CHECKPOINT_BYTES = 20 * 1024 * 1024;
4624
4701
  var CHECKPOINT_TTL_MS = 24 * 60 * 60 * 1e3;
@@ -4699,6 +4776,40 @@ function resultOutboxActiveRunCommands(config) {
4699
4776
  }
4700
4777
  return entries;
4701
4778
  }
4779
+ function resultOutboxFailedRunCommands(config) {
4780
+ const dir = resultOutboxInvalidDir(config);
4781
+ if (!existsSync9(dir)) return [];
4782
+ const entries = [];
4783
+ for (const file of readdirSync6(dir).filter((name) => name.endsWith(".json")).sort().slice(-100)) {
4784
+ let entry;
4785
+ try {
4786
+ entry = asRecord(JSON.parse(readFileSync7(join10(dir, file), "utf8")));
4787
+ } catch {
4788
+ continue;
4789
+ }
4790
+ const failureReason = readString(entry.invalidReason);
4791
+ if (!failureReason || !["server_rejected_terminal", "retry_exhausted"].includes(failureReason)) continue;
4792
+ const activeRun = asRecord(entry.activeRun);
4793
+ const commandId = readString(activeRun.commandId) ?? readString(entry.commandId);
4794
+ if (!commandId) continue;
4795
+ entries.push({
4796
+ commandId,
4797
+ ...readString(activeRun.runId) ? { runId: readString(activeRun.runId) } : {},
4798
+ ...readString(activeRun.issueId) ? { issueId: readString(activeRun.issueId) } : {},
4799
+ ...readString(activeRun.executorKind) ? { executorKind: readString(activeRun.executorKind) } : {},
4800
+ ...readString(activeRun.workspacePath) ? { workspacePath: readString(activeRun.workspacePath) } : {},
4801
+ ...readString(activeRun.startedAt) ? { startedAt: readString(activeRun.startedAt) } : {},
4802
+ ...readString(activeRun.executorCompletedAt) ? { executorCompletedAt: readString(activeRun.executorCompletedAt) } : {},
4803
+ phase: "result_outbox_failed",
4804
+ outboxPending: 0,
4805
+ evidenceDeliveryStatus: "failed",
4806
+ evidenceFailureReason: failureReason,
4807
+ ...typeof entry.httpStatus === "number" ? { evidenceHttpStatus: entry.httpStatus } : {},
4808
+ ...readString(entry.invalidAt) ? { evidenceFailedAt: readString(entry.invalidAt) } : {}
4809
+ });
4810
+ }
4811
+ return entries;
4812
+ }
4702
4813
  function clearDeliveredResultOutboxCommand(config, entry) {
4703
4814
  const commandId = readString(entry?.commandId);
4704
4815
  if (commandId) pendingResultOutboxRunCommands.delete(commandId);
@@ -5001,8 +5112,10 @@ function buildHeartbeatPayload(config, options = {}) {
5001
5112
  const orphanReaperSummary = asRecord(options.orphanReaper ?? lastOrphanReaperSummary);
5002
5113
  const activeRunCommandById = /* @__PURE__ */ new Map();
5003
5114
  for (const entry of [
5115
+ ...Array.from(localDispatchCommandIds, (commandId) => ({ commandId, phase: "preparing" })),
5004
5116
  ...Array.from(activeRunCommands.values()),
5005
5117
  ...Array.from(pendingResultOutboxRunCommands.values()),
5118
+ ...resultOutboxFailedRunCommands(config),
5006
5119
  ...resultOutboxActiveRunCommands(config)
5007
5120
  ]) {
5008
5121
  const commandId = readString(entry.commandId);
@@ -5993,7 +6106,7 @@ function buildExecutorInvocation(executor, command = {}, workspace = null) {
5993
6106
  }
5994
6107
  return { command: executor.command, args: [], stdin: "prompt" };
5995
6108
  }
5996
- async function executeModelCallCommand(config, command) {
6109
+ async function executeModelCallCommand(config, command, signal) {
5997
6110
  const executor = selectExecutor(config, command);
5998
6111
  const payload = asRecord(command.payload);
5999
6112
  const prompt = readString(payload.prompt) ?? "";
@@ -6009,6 +6122,7 @@ async function executeModelCallCommand(config, command) {
6009
6122
  config.executorMaxOutputBytes,
6010
6123
  readNumber(payload.maxOutputBytes, 512 * 1024)
6011
6124
  ));
6125
+ await ackCommand(config, command, "spawned");
6012
6126
  await ingestLog(config, command, "system", "info", `Starting ${executor.kind} runtime model call`, {
6013
6127
  executorKind: executor.kind,
6014
6128
  args: invocation.args,
@@ -6021,7 +6135,8 @@ async function executeModelCallCommand(config, command) {
6021
6135
  stdin: invocation.stdin === "prompt" ? prompt : "",
6022
6136
  timeoutSeconds,
6023
6137
  maxOutputBytes,
6024
- executorKind: executor.kind
6138
+ executorKind: executor.kind,
6139
+ signal
6025
6140
  });
6026
6141
  if (execution.stdout) {
6027
6142
  await ingestLog(config, command, "stdout", "info", truncateText(execution.stdout, 4e3));
@@ -6059,6 +6174,17 @@ async function executeModelCallCommand(config, command) {
6059
6174
  } : {}
6060
6175
  }, error ?? void 0);
6061
6176
  }
6177
+ async function executeTrackedModelCallCommand(config, command) {
6178
+ const abortController = new AbortController();
6179
+ const forgetActiveModelCall = rememberActiveRunCommand(command, {});
6180
+ const stopActiveModelCallHeartbeats = startActiveRunHeartbeats(config, command, abortController);
6181
+ try {
6182
+ return await executeModelCallCommand(config, command, abortController.signal);
6183
+ } finally {
6184
+ stopActiveModelCallHeartbeats();
6185
+ forgetActiveModelCall();
6186
+ }
6187
+ }
6062
6188
  function redactProtectedText(value, protectedValues = []) {
6063
6189
  let text = String(value ?? "");
6064
6190
  for (const protectedValue of protectedValues) {
@@ -6066,7 +6192,7 @@ function redactProtectedText(value, protectedValues = []) {
6066
6192
  }
6067
6193
  return text;
6068
6194
  }
6069
- function createLiveOutputLogger(config, command, executorKind, protectedValues = []) {
6195
+ function createLiveOutputLogger(config, command, executorKind, protectedValues = [], onMcpToolResults = null) {
6070
6196
  const buffers = { stdout: "", stderr: "" };
6071
6197
  const displayCounts = { stdout: 0, stderr: 0, system: 0 };
6072
6198
  const sourceCounts = { stdout: 0, stderr: 0 };
@@ -6164,7 +6290,11 @@ function createLiveOutputLogger(config, command, executorKind, protectedValues =
6164
6290
  if (executorKind === "pi" && readString(asRecord(event).type) === "session") {
6165
6291
  executorSessionId = readString(asRecord(event).sessionId ?? asRecord(event).id) ?? executorSessionId;
6166
6292
  }
6167
- if (executorKind === "pi" && event) liveMcpToolResults.push(...piMcpToolResults(event));
6293
+ if (executorKind === "pi" && event) {
6294
+ const results = piMcpToolResults(event);
6295
+ liveMcpToolResults.push(...results);
6296
+ if (results.length > 0) onMcpToolResults?.(results);
6297
+ }
6168
6298
  let entry = null;
6169
6299
  if (event && shouldPreserveExecutorJsonlForTranscript(executorKind, event)) {
6170
6300
  entry = {
@@ -6305,6 +6435,7 @@ function realOrResolvedPath(value) {
6305
6435
  return resolve8(value);
6306
6436
  }
6307
6437
  }
6438
+ var LSOF_COMMAND = process.platform === "darwin" && existsSync9("/usr/sbin/lsof") ? "/usr/sbin/lsof" : "lsof";
6308
6439
  function processCwdForPid(pid) {
6309
6440
  if (process.platform === "linux") {
6310
6441
  try {
@@ -6314,7 +6445,7 @@ function processCwdForPid(pid) {
6314
6445
  }
6315
6446
  }
6316
6447
  if (process.platform === "darwin") {
6317
- const result2 = spawnSync5("lsof", ["-a", "-p", String(pid), "-d", "cwd", "-Fn"], {
6448
+ const result2 = spawnSync5(LSOF_COMMAND, ["-a", "-p", String(pid), "-d", "cwd", "-Fn"], {
6318
6449
  encoding: "utf8",
6319
6450
  stdio: ["ignore", "pipe", "ignore"]
6320
6451
  });
@@ -6344,7 +6475,7 @@ function allProcessCwdsByPid() {
6344
6475
  return cwds;
6345
6476
  }
6346
6477
  if (process.platform === "darwin") {
6347
- const result2 = spawnSync5("lsof", ["-nP", "-d", "cwd", "-Fp", "-Fn"], {
6478
+ const result2 = spawnSync5(LSOF_COMMAND, ["-nP", "-d", "cwd", "-Fp", "-Fn"], {
6348
6479
  encoding: "utf8",
6349
6480
  stdio: ["ignore", "pipe", "ignore"]
6350
6481
  });
@@ -6514,8 +6645,8 @@ function runOrphanReaper(config, options = {}) {
6514
6645
  return finishSummary();
6515
6646
  }
6516
6647
  const refs = activeRuntimeExecutionRefs();
6517
- const processRows = allProcessRows();
6518
- const processCwdsByPid = allProcessCwdsByPid();
6648
+ let processRows = null;
6649
+ let processCwdsByPid = null;
6519
6650
  for (const workdir of walkManagedWorkdirs(config.runtimeWorkspacesRoot)) {
6520
6651
  const manifest = readWorkspaceManifest(workspaceManifestPath(workdir));
6521
6652
  if (!manifest || manifest.managed !== true) continue;
@@ -6524,6 +6655,8 @@ function runOrphanReaper(config, options = {}) {
6524
6655
  summary.protectedWorkdirCount += 1;
6525
6656
  continue;
6526
6657
  }
6658
+ processRows ??= allProcessRows();
6659
+ processCwdsByPid ??= allProcessCwdsByPid();
6527
6660
  const residents = listWorkspaceResidentProcesses(workdir, null, { processRows, processCwdsByPid });
6528
6661
  if (residents.length === 0) continue;
6529
6662
  summary.orphanWorkdirCount += 1;
@@ -6562,8 +6695,19 @@ function runExecutor(command, args, options) {
6562
6695
  let timer = null;
6563
6696
  let rssTimer = null;
6564
6697
  let stopKillTimer = null;
6565
- let stoppedExitDrainTimer = null;
6698
+ let quiescenceTimer = null;
6699
+ let stoppedResidentTimer = null;
6566
6700
  let completionOutputDrainTimer = null;
6701
+ let stopReason = null;
6702
+ let childExited = false;
6703
+ let childClosed = false;
6704
+ let closeCode = null;
6705
+ let closeSignal = null;
6706
+ let spawnError = null;
6707
+ let outputDrainForcedClosed = false;
6708
+ let killedWorkspaceResidents = [];
6709
+ let stoppedResidentDrainUntil = 0;
6710
+ const residentSignalledAt = /* @__PURE__ */ new Map();
6567
6711
  let piCompletionOutputLineBuffer = "";
6568
6712
  const outputBytes = { stdout: 0, stderr: 0 };
6569
6713
  const maxOutputBytes = parsePositiveInteger(options.maxOutputBytes, 50 * 1024 * 1024);
@@ -6582,11 +6726,10 @@ function runExecutor(command, args, options) {
6582
6726
  if (timer) clearTimeout(timer);
6583
6727
  if (rssTimer) clearInterval(rssTimer);
6584
6728
  if (stopKillTimer) clearTimeout(stopKillTimer);
6585
- if (stoppedExitDrainTimer) clearTimeout(stoppedExitDrainTimer);
6729
+ if (quiescenceTimer) clearTimeout(quiescenceTimer);
6730
+ if (stoppedResidentTimer) clearTimeout(stoppedResidentTimer);
6586
6731
  if (completionOutputDrainTimer) clearTimeout(completionOutputDrainTimer);
6587
6732
  options.signal?.removeEventListener?.("abort", abort);
6588
- const shouldCleanupWorkspaceResidents = Boolean(aborted || outputFlood || memoryLimit || result2.timedOut || completionOutputType);
6589
- const killedWorkspaceResidents = shouldCleanupWorkspaceResidents ? killWorkspaceResidentProcesses(options.cwd, processGroupId) : [];
6590
6733
  resolveRun({
6591
6734
  stdout,
6592
6735
  stderr,
@@ -6595,25 +6738,77 @@ function runExecutor(command, args, options) {
6595
6738
  memoryLimit,
6596
6739
  completionOutputType,
6597
6740
  killedWorkspaceResidents,
6741
+ ...outputDrainForcedClosed ? { outputDrainForcedClosed: true } : {},
6598
6742
  ...result2
6599
6743
  });
6600
6744
  };
6601
6745
  const scheduleStopKill = () => {
6602
- if (stopKillTimer) clearTimeout(stopKillTimer);
6746
+ if (stopKillTimer) return;
6603
6747
  stopKillTimer = setTimeout(() => signalExecutorProcess(child, "SIGKILL", processGroupId), 2e3);
6604
- stopKillTimer.unref?.();
6605
6748
  };
6606
- const destroyExecutorOutputPipes = () => {
6607
- child.stdout.destroy();
6608
- child.stderr.destroy();
6749
+ const requestStop = (reason) => {
6750
+ if (settled || stopReason) return;
6751
+ stopReason = reason;
6752
+ signalExecutorProcess(child, "SIGTERM", processGroupId);
6753
+ scheduleStopKill();
6609
6754
  };
6610
- const finishStoppedExecutorAfterExit = (code, signal) => {
6611
- if (settled || !aborted && !outputFlood && !memoryLimit && !(completionOutputType && signal === "SIGTERM") || stoppedExitDrainTimer) return;
6612
- stoppedExitDrainTimer = setTimeout(() => {
6613
- destroyExecutorOutputPipes();
6614
- finish({ exitCode: code, signal, timedOut: false, spawnError: null, cancelled: aborted });
6615
- }, 100);
6616
- stoppedExitDrainTimer.unref?.();
6755
+ const reapStoppedWorkspaceResidents = () => {
6756
+ if (settled || !stopReason || stopReason === "completion_cleanup") return;
6757
+ const residents = listWorkspaceResidentProcesses(options.cwd, processGroupId, {
6758
+ processRows: allProcessRows(),
6759
+ processCwdsByPid: allProcessCwdsByPid()
6760
+ });
6761
+ const seen = new Set(killedWorkspaceResidents.map((resident) => resident.pid));
6762
+ const now = Date.now();
6763
+ for (const resident of residents) {
6764
+ if (!seen.has(resident.pid)) killedWorkspaceResidents.push(resident);
6765
+ if (!residentSignalledAt.has(resident.pid)) {
6766
+ residentSignalledAt.set(resident.pid, now);
6767
+ try {
6768
+ process.kill(resident.pid, "SIGTERM");
6769
+ } catch {
6770
+ }
6771
+ }
6772
+ }
6773
+ for (const resident of killedWorkspaceResidents) {
6774
+ const signalledAt = residentSignalledAt.get(resident.pid) ?? now;
6775
+ if (now - signalledAt < 2e3 || !isProcessIdAlive(resident.pid)) continue;
6776
+ try {
6777
+ process.kill(resident.pid, "SIGKILL");
6778
+ } catch {
6779
+ }
6780
+ }
6781
+ if (isExecutorProcessAlive(child, processGroupId) || now < stoppedResidentDrainUntil || killedWorkspaceResidents.some((resident) => isProcessIdAlive(resident.pid))) {
6782
+ stoppedResidentTimer = setTimeout(reapStoppedWorkspaceResidents, 100);
6783
+ } else {
6784
+ stoppedResidentTimer = null;
6785
+ }
6786
+ maybeFinishQuiescent();
6787
+ };
6788
+ const maybeFinishQuiescent = () => {
6789
+ if (settled || !childExited && !childClosed) return;
6790
+ if (isExecutorProcessAlive(child, processGroupId) || !childClosed && Date.now() < stoppedResidentDrainUntil || killedWorkspaceResidents.some((resident) => isProcessIdAlive(resident.pid))) {
6791
+ requestStop("completion_cleanup");
6792
+ if (!quiescenceTimer) {
6793
+ quiescenceTimer = setTimeout(() => {
6794
+ quiescenceTimer = null;
6795
+ maybeFinishQuiescent();
6796
+ }, 25);
6797
+ }
6798
+ return;
6799
+ }
6800
+ if (!childClosed) {
6801
+ outputDrainForcedClosed = true;
6802
+ child.stdout.destroy();
6803
+ child.stderr.destroy();
6804
+ }
6805
+ finish({
6806
+ exitCode: closeCode,
6807
+ signal: closeSignal,
6808
+ timedOut: stopReason === "timeout",
6809
+ spawnError,
6810
+ cancelled: aborted
6811
+ });
6617
6812
  };
6618
6813
  const maybeSchedulePiCompletionDrain = (text) => {
6619
6814
  if (settled || outputFlood || options.executorKind !== "pi" || completionOutputType) return;
@@ -6638,8 +6833,7 @@ function runExecutor(command, args, options) {
6638
6833
  completionOutputType = stopType;
6639
6834
  completionOutputDrainTimer = setTimeout(() => {
6640
6835
  if (settled) return;
6641
- signalExecutorProcess(child, "SIGTERM", processGroupId);
6642
- scheduleStopKill();
6836
+ requestStop("completion_output");
6643
6837
  }, PI_COMPLETION_OUTPUT_GRACE_MS);
6644
6838
  completionOutputDrainTimer.unref?.();
6645
6839
  return;
@@ -6655,8 +6849,7 @@ function runExecutor(command, args, options) {
6655
6849
  bytes: outputBytes[stream],
6656
6850
  limitBytes: maxOutputBytes
6657
6851
  };
6658
- signalExecutorProcess(child, "SIGTERM", processGroupId);
6659
- scheduleStopKill();
6852
+ requestStop("output_flood");
6660
6853
  };
6661
6854
  const killForMemoryLimit = (rssBytes) => {
6662
6855
  if (settled || memoryLimit) return;
@@ -6664,23 +6857,17 @@ function runExecutor(command, args, options) {
6664
6857
  rssBytes,
6665
6858
  limitBytes: maxRssBytes
6666
6859
  };
6667
- signalExecutorProcess(child, "SIGTERM", processGroupId);
6668
- scheduleStopKill();
6860
+ requestStop("memory_limit");
6669
6861
  };
6670
6862
  const abort = () => {
6671
6863
  if (settled) return;
6672
6864
  aborted = true;
6673
- signalExecutorProcess(child, "SIGTERM", processGroupId);
6674
- scheduleStopKill();
6865
+ requestStop("cancel");
6675
6866
  };
6676
6867
  options.signal?.addEventListener?.("abort", abort, { once: true });
6677
6868
  if (options.signal?.aborted) abort();
6678
6869
  timer = setTimeout(() => {
6679
- signalExecutorProcess(child, "SIGTERM", processGroupId);
6680
- const timeoutKillTimer = setTimeout(() => signalExecutorProcess(child, "SIGKILL", processGroupId), 2e3);
6681
- timeoutKillTimer.unref?.();
6682
- destroyExecutorOutputPipes();
6683
- finish({ exitCode: null, signal: "SIGTERM", timedOut: true, spawnError: null });
6870
+ requestStop("timeout");
6684
6871
  }, options.timeoutSeconds * 1e3);
6685
6872
  if (maxRssMb > 0 && processGroupId !== null) {
6686
6873
  rssTimer = setInterval(() => {
@@ -6707,13 +6894,27 @@ function runExecutor(command, args, options) {
6707
6894
  if (outputBytes.stderr > maxOutputBytes) killForOutputFlood("stderr");
6708
6895
  });
6709
6896
  child.on("error", (err) => {
6710
- finish({ exitCode: null, signal: null, timedOut: false, spawnError: err.message });
6897
+ spawnError = err.message;
6711
6898
  });
6712
6899
  child.on("exit", (code, signal) => {
6713
- finishStoppedExecutorAfterExit(code, signal);
6900
+ childExited = true;
6901
+ closeCode = code;
6902
+ closeSignal = signal;
6903
+ stoppedResidentDrainUntil = Date.now() + 100;
6904
+ if (!stopReason && isExecutorProcessAlive(child, processGroupId)) {
6905
+ requestStop("completion_cleanup");
6906
+ }
6907
+ if (stopReason && stopReason !== "completion_cleanup") {
6908
+ stoppedResidentDrainUntil = Date.now() + 100;
6909
+ reapStoppedWorkspaceResidents();
6910
+ }
6911
+ maybeFinishQuiescent();
6714
6912
  });
6715
6913
  child.on("close", (code, signal) => {
6716
- finish({ exitCode: code, signal, timedOut: false, spawnError: null, cancelled: aborted });
6914
+ childClosed = true;
6915
+ closeCode = code;
6916
+ closeSignal = signal;
6917
+ maybeFinishQuiescent();
6717
6918
  });
6718
6919
  if (options.stdin) child.stdin.end(options.stdin);
6719
6920
  else child.stdin.end();
@@ -7516,10 +7717,12 @@ function piOutputValidationError(parsed, options = {}) {
7516
7717
  }
7517
7718
  return null;
7518
7719
  }
7519
- async function ackCommand(config, command) {
7720
+ async function ackCommand(config, command, phase = "preparing") {
7520
7721
  const connectorId = requireConnectorId(config);
7521
- return await postJson(config, `/api/amaster/runtime-connectors/${connectorId}/commands/${command.commandId}/ack`, {
7522
- leaseId: command.leaseId
7722
+ return await postJsonWithRetry(config, `/api/amaster/runtime-connectors/${connectorId}/commands/${command.commandId}/ack`, {
7723
+ leaseId: command.leaseId,
7724
+ generation: command.generation,
7725
+ phase
7523
7726
  });
7524
7727
  }
7525
7728
  async function executeRunCommand(config, command) {
@@ -7608,6 +7811,7 @@ async function executeRunCommand(config, command) {
7608
7811
  presentationKind: "context_manifest",
7609
7812
  contextManifest
7610
7813
  });
7814
+ await ackCommand(config, command, "spawned");
7611
7815
  await ingestLog(config, command, "system", "info", `Starting ${executor.kind} executor`, {
7612
7816
  executorKind: executor.kind,
7613
7817
  cwd,
@@ -7625,7 +7829,18 @@ async function executeRunCommand(config, command) {
7625
7829
  const abortController = new AbortController();
7626
7830
  const stopActiveRunHeartbeats = startActiveRunHeartbeats(config, command, abortController);
7627
7831
  const protectedExecutorValues = managedMcpProfile?.protectedValues ?? (managedMcpProfile ? [governedMcp.sessionToken] : []);
7628
- const liveOutputLogger = createLiveOutputLogger(config, command, executor.kind, protectedExecutorValues);
7832
+ const runtimeArtifacts = [];
7833
+ const runtimeArtifactIngest = createRuntimeArtifactIngestQueue({
7834
+ ingest: (results) => ingestRuntimeArtifacts(config, command, cwd, results),
7835
+ onError: () => abortController.abort()
7836
+ });
7837
+ const liveOutputLogger = createLiveOutputLogger(
7838
+ config,
7839
+ command,
7840
+ executor.kind,
7841
+ protectedExecutorValues,
7842
+ (results) => runtimeArtifactIngest.enqueue(results)
7843
+ );
7629
7844
  let execution;
7630
7845
  try {
7631
7846
  try {
@@ -7646,6 +7861,7 @@ async function executeRunCommand(config, command) {
7646
7861
  } finally {
7647
7862
  await liveOutputLogger.flush();
7648
7863
  }
7864
+ runtimeArtifacts.push(...await runtimeArtifactIngest.flush());
7649
7865
  execution.stdout = redactProtectedText(execution.stdout, protectedExecutorValues);
7650
7866
  execution.stderr = redactProtectedText(execution.stderr, protectedExecutorValues);
7651
7867
  patchActiveRunCommand(command, {
@@ -7702,7 +7918,15 @@ async function executeRunCommand(config, command) {
7702
7918
  ...Array.isArray(parsed.mcpToolResults) ? parsed.mcpToolResults.map(asRecord) : [],
7703
7919
  ...liveOutputLogger.mcpToolResults().map(asRecord)
7704
7920
  ];
7705
- const runtimeArtifacts = await ingestRuntimeArtifacts(config, command, cwd, mcpToolResults);
7921
+ runtimeArtifacts.push(...await ingestRuntimeArtifacts(
7922
+ config,
7923
+ command,
7924
+ cwd,
7925
+ mcpToolResults.filter((result3) => {
7926
+ const intentId = readString(asRecord(result3).artifactIntent?.intentId);
7927
+ return !intentId || !runtimeArtifactIngest.hasHandled(intentId);
7928
+ })
7929
+ ));
7706
7930
  const shouldPreserveNativeSession = managedMcpProfile && parsed.sessionId && (execution.exitCode === 0 || execution.completionOutputType === "approval_required") && !execution.timedOut && execution.cancelled !== true && !execution.spawnError && ["codex", "pi"].includes(executor.kind) && mcpToolResults.some((result3) => readString(result3.status) === "approval_required");
7707
7931
  if (shouldPreserveNativeSession) {
7708
7932
  try {
@@ -7796,6 +8020,7 @@ async function executeRunCommand(config, command) {
7796
8020
  });
7797
8021
  }
7798
8022
  const result2 = {
8023
+ evidenceContract: { version: 1 },
7799
8024
  executorKind: executor.kind,
7800
8025
  command: invocation.command,
7801
8026
  args: invocation.args,
@@ -7805,6 +8030,7 @@ async function executeRunCommand(config, command) {
7805
8030
  exitCode: execution.exitCode,
7806
8031
  signal: execution.signal,
7807
8032
  timedOut: execution.timedOut,
8033
+ ...execution.outputDrainForcedClosed ? { outputDrainForcedClosed: true } : {},
7808
8034
  ...readString(execution.completionOutputType) ? { completionOutputType: readString(execution.completionOutputType) } : {},
7809
8035
  ...cancelled ? { cancelledByControlPlane: true } : {},
7810
8036
  ...invocation.nativeSession ? { nativeSession: invocation.nativeSession } : {},
@@ -7900,7 +8126,7 @@ async function processCommand(config, command) {
7900
8126
  return;
7901
8127
  }
7902
8128
  if (command.commandType === "model_call") {
7903
- await executeModelCallCommand(config, command);
8129
+ await executeTrackedModelCallCommand(config, command);
7904
8130
  return;
7905
8131
  }
7906
8132
  await completeCommand(config, command, "succeeded", {
@@ -5,7 +5,7 @@ import { dirname, join, resolve } from "node:path";
5
5
  import { homedir, hostname } from "node:os";
6
6
  import { fileURLToPath } from "node:url";
7
7
 
8
- const CONNECTOR_VERSION = "0.1.0-beta.22";
8
+ const CONNECTOR_VERSION = "0.1.0-beta.24";
9
9
 
10
10
  const CAPABILITIES = [
11
11
  "remote_registration",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@amaster.ai/employee-runtime-connector",
3
- "version": "0.1.0-beta.22",
3
+ "version": "0.1.0-beta.24",
4
4
  "description": "AMaster Employee runtime connector CLI and daemon",
5
5
  "license": "MIT",
6
6
  "type": "module",