@ai-development-environment/control-agent 0.0.78 → 0.0.79

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.
Files changed (2) hide show
  1. package/dist/control-agent.js +196 -100
  2. package/package.json +1 -1
@@ -2276,7 +2276,7 @@ var require_websocket = __commonJS({
2276
2276
  var http = __require("http");
2277
2277
  var net = __require("net");
2278
2278
  var tls = __require("tls");
2279
- var { randomBytes, createHash: createHash6 } = __require("crypto");
2279
+ var { randomBytes, createHash: createHash7 } = __require("crypto");
2280
2280
  var { Duplex, Readable: Readable2 } = __require("stream");
2281
2281
  var { URL: URL2 } = __require("url");
2282
2282
  var PerMessageDeflate2 = require_permessage_deflate();
@@ -2944,7 +2944,7 @@ var require_websocket = __commonJS({
2944
2944
  abortHandshake(websocket, socket, "Invalid Upgrade header");
2945
2945
  return;
2946
2946
  }
2947
- const digest = createHash6("sha1").update(key + GUID).digest("base64");
2947
+ const digest = createHash7("sha1").update(key + GUID).digest("base64");
2948
2948
  if (res.headers["sec-websocket-accept"] !== digest) {
2949
2949
  abortHandshake(websocket, socket, "Invalid Sec-WebSocket-Accept header");
2950
2950
  return;
@@ -3313,7 +3313,7 @@ var require_websocket_server = __commonJS({
3313
3313
  var EventEmitter = __require("events");
3314
3314
  var http = __require("http");
3315
3315
  var { Duplex } = __require("stream");
3316
- var { createHash: createHash6 } = __require("crypto");
3316
+ var { createHash: createHash7 } = __require("crypto");
3317
3317
  var extension2 = require_extension();
3318
3318
  var PerMessageDeflate2 = require_permessage_deflate();
3319
3319
  var subprotocol2 = require_subprotocol();
@@ -3620,7 +3620,7 @@ var require_websocket_server = __commonJS({
3620
3620
  );
3621
3621
  }
3622
3622
  if (this._state > RUNNING) return abortHandshake(socket, 503);
3623
- const digest = createHash6("sha1").update(key + GUID).digest("base64");
3623
+ const digest = createHash7("sha1").update(key + GUID).digest("base64");
3624
3624
  const headers = [
3625
3625
  "HTTP/1.1 101 Switching Protocols",
3626
3626
  "Upgrade: websocket",
@@ -8283,10 +8283,183 @@ function collectInventory() {
8283
8283
  }
8284
8284
 
8285
8285
  // src/handlers/commands.ts
8286
- import { spawn } from "node:child_process";
8286
+ import { spawn as spawn2 } from "node:child_process";
8287
8287
  import { chmod as chmod2, mkdtemp, realpath, rm as rm2, writeFile as writeFile2 } from "node:fs/promises";
8288
8288
  import { homedir as homedir2, tmpdir } from "node:os";
8289
8289
  import { join as join2 } from "node:path";
8290
+
8291
+ // src/command-processes.ts
8292
+ import { createHash as createHash3 } from "node:crypto";
8293
+
8294
+ // src/capture-command.ts
8295
+ import { spawn } from "node:child_process";
8296
+ var MAX_OUTPUT_BYTES = 2 * 1024 * 1024;
8297
+ function captureCommand(options) {
8298
+ if (options.signal.aborted || options.timeoutMs <= 0) {
8299
+ return Promise.resolve({
8300
+ exitCode: null,
8301
+ signal: null,
8302
+ timedOut: !options.signal.aborted,
8303
+ cancelled: options.signal.aborted,
8304
+ stdout: "",
8305
+ stderr: ""
8306
+ });
8307
+ }
8308
+ return new Promise((resolve8, reject) => {
8309
+ let stdout = "";
8310
+ let stderr = "";
8311
+ let timedOut = false;
8312
+ let cancelled = false;
8313
+ let settled = false;
8314
+ let killTimer = null;
8315
+ const child = spawn(options.command, options.args, {
8316
+ shell: false,
8317
+ stdio: [
8318
+ "ignore",
8319
+ options.stdoutFileDescriptor === void 0 ? "pipe" : options.stdoutFileDescriptor,
8320
+ "pipe"
8321
+ ],
8322
+ env: options.env ?? process.env,
8323
+ cwd: options.cwd
8324
+ });
8325
+ const append = (current, chunk) => `${current}${String(chunk)}`.slice(0, MAX_OUTPUT_BYTES);
8326
+ child.stdout?.on("data", (chunk) => {
8327
+ stdout = append(stdout, chunk);
8328
+ });
8329
+ child.stderr?.on("data", (chunk) => {
8330
+ stderr = append(stderr, chunk);
8331
+ });
8332
+ const terminate = () => {
8333
+ if (child.exitCode !== null || child.killed) return;
8334
+ child.kill("SIGTERM");
8335
+ killTimer = setTimeout(() => {
8336
+ if (child.exitCode === null) child.kill("SIGKILL");
8337
+ }, 5e3);
8338
+ killTimer.unref();
8339
+ };
8340
+ const timeout = setTimeout(() => {
8341
+ timedOut = true;
8342
+ terminate();
8343
+ }, options.timeoutMs);
8344
+ timeout.unref();
8345
+ const abort = () => {
8346
+ cancelled = true;
8347
+ terminate();
8348
+ };
8349
+ options.signal.addEventListener("abort", abort, { once: true });
8350
+ if (options.signal.aborted) abort();
8351
+ child.once("error", (error) => {
8352
+ if (settled) return;
8353
+ settled = true;
8354
+ clearTimeout(timeout);
8355
+ if (killTimer) clearTimeout(killTimer);
8356
+ options.signal.removeEventListener("abort", abort);
8357
+ reject(error);
8358
+ });
8359
+ child.once("close", (exitCode, signal) => {
8360
+ if (settled) return;
8361
+ settled = true;
8362
+ clearTimeout(timeout);
8363
+ if (killTimer) clearTimeout(killTimer);
8364
+ options.signal.removeEventListener("abort", abort);
8365
+ resolve8({
8366
+ exitCode,
8367
+ signal,
8368
+ timedOut,
8369
+ cancelled,
8370
+ stdout,
8371
+ stderr
8372
+ });
8373
+ });
8374
+ });
8375
+ }
8376
+
8377
+ // src/command-processes.ts
8378
+ var COMMAND_SCRIPT_PREFIX = "aide-command-";
8379
+ var TERMINATE_GRACE_MS = 5e3;
8380
+ function commandScriptPrefix(agentId) {
8381
+ const marker = createHash3("sha256").update(agentId).digest("hex").slice(0, 16);
8382
+ return `${COMMAND_SCRIPT_PREFIX}${marker}-`;
8383
+ }
8384
+ var scriptPattern = (agentId) => `${commandScriptPrefix(agentId)}[^/]*/command\\.sh`;
8385
+ function parseCommandProcesses(output, ownPid) {
8386
+ const groups = /* @__PURE__ */ new Map();
8387
+ for (const line of output.split("\n")) {
8388
+ const [rawPid, rawGroup] = line.trim().split(/\s+/);
8389
+ const pid = Number(rawPid);
8390
+ const processGroup = Number(rawGroup);
8391
+ if (!Number.isInteger(pid) || !Number.isInteger(processGroup)) continue;
8392
+ if (processGroup <= 1 || pid <= 1) continue;
8393
+ if (pid === ownPid || processGroup === ownPid) continue;
8394
+ const known = groups.get(processGroup);
8395
+ if (!known || pid === processGroup) {
8396
+ groups.set(processGroup, { pid, processGroup });
8397
+ }
8398
+ }
8399
+ return [...groups.values()];
8400
+ }
8401
+ async function listCommandProcesses(agentId, signal) {
8402
+ const found = await captureCommand({
8403
+ command: "/usr/bin/pgrep",
8404
+ args: ["-f", scriptPattern(agentId)],
8405
+ timeoutMs: 5e3,
8406
+ signal
8407
+ });
8408
+ const pids = found.stdout.split("\n").map((line) => line.trim()).filter((line) => /^\d+$/.test(line));
8409
+ if (!pids.length) return [];
8410
+ const described = await captureCommand({
8411
+ command: "/bin/ps",
8412
+ args: ["-o", "pid=,pgid=", "-p", pids.join(",")],
8413
+ timeoutMs: 5e3,
8414
+ signal
8415
+ });
8416
+ return parseCommandProcesses(described.stdout, process.pid);
8417
+ }
8418
+ function signalGroup(processGroup, name) {
8419
+ try {
8420
+ process.kill(-processGroup, name);
8421
+ return true;
8422
+ } catch (error) {
8423
+ if (error instanceof Error && "code" in error && error.code === "ESRCH") {
8424
+ return false;
8425
+ }
8426
+ console.error(
8427
+ `Could not signal orphaned command process group ${processGroup}:`,
8428
+ error instanceof Error ? error.message : error
8429
+ );
8430
+ return false;
8431
+ }
8432
+ }
8433
+ async function reapOrphanedCommandProcesses(agentId, signal = new AbortController().signal) {
8434
+ if (process.platform === "win32") return [];
8435
+ let orphans;
8436
+ try {
8437
+ orphans = await listCommandProcesses(agentId, signal);
8438
+ } catch (error) {
8439
+ console.error(
8440
+ "Could not look for orphaned command processes:",
8441
+ error instanceof Error ? error.message : error
8442
+ );
8443
+ return [];
8444
+ }
8445
+ const signalled = orphans.filter(
8446
+ (orphan) => signalGroup(orphan.processGroup, "SIGTERM")
8447
+ );
8448
+ if (!signalled.length) return [];
8449
+ console.error(
8450
+ `Terminating ${signalled.length} command process(es) left by a previous agent run.`
8451
+ );
8452
+ await new Promise((resolve8) => {
8453
+ const timer = setTimeout(resolve8, TERMINATE_GRACE_MS);
8454
+ timer.unref();
8455
+ if (signal.aborted) resolve8();
8456
+ else signal.addEventListener("abort", () => resolve8(), { once: true });
8457
+ });
8458
+ for (const orphan of signalled) signalGroup(orphan.processGroup, "SIGKILL");
8459
+ return signalled;
8460
+ }
8461
+
8462
+ // src/handlers/commands.ts
8290
8463
  var MAX_BATCH_BYTES = 128 * 1024;
8291
8464
  var FLUSH_DELAY_MS = 25;
8292
8465
  var MAX_UPLOAD_ATTEMPTS = 8;
@@ -8295,7 +8468,9 @@ var runCommand = async (rawPayload, _timeoutMs, signal, _onLog, context) => {
8295
8468
  if (!context?.appendCommandOutput) {
8296
8469
  throw new Error("Command output uploader is unavailable");
8297
8470
  }
8298
- const directory = await mkdtemp(join2(tmpdir(), "aide-command-"));
8471
+ const directory = await mkdtemp(
8472
+ join2(tmpdir(), commandScriptPrefix(context.agentId))
8473
+ );
8299
8474
  const scriptPath = join2(directory, "command.sh");
8300
8475
  await writeFile2(scriptPath, payload.script, {
8301
8476
  encoding: "utf8",
@@ -8382,7 +8557,7 @@ var runCommand = async (rawPayload, _timeoutMs, signal, _onLog, context) => {
8382
8557
  return await new Promise((resolve8, reject) => {
8383
8558
  let cancelled = false;
8384
8559
  let settled = false;
8385
- const child = spawn(process.env.SHELL || "/bin/sh", ["-l", scriptPath], {
8560
+ const child = spawn2(process.env.SHELL || "/bin/sh", ["-l", scriptPath], {
8386
8561
  cwd,
8387
8562
  detached: process.platform !== "win32",
8388
8563
  env: {
@@ -8466,7 +8641,7 @@ import { readFile as readFile3, realpath as realpath3, stat as stat3 } from "nod
8466
8641
  import { join as join4, relative as relative4, sep as sep4 } from "node:path";
8467
8642
 
8468
8643
  // src/handlers/builds.ts
8469
- import { createHash as createHash4, randomUUID } from "node:crypto";
8644
+ import { createHash as createHash5, randomUUID } from "node:crypto";
8470
8645
  import {
8471
8646
  cp,
8472
8647
  mkdir as mkdir2,
@@ -8643,91 +8818,8 @@ function parsePlist(xml) {
8643
8818
  return parseValue(xml, from, 0)[0];
8644
8819
  }
8645
8820
 
8646
- // src/capture-command.ts
8647
- import { spawn as spawn2 } from "node:child_process";
8648
- var MAX_OUTPUT_BYTES = 2 * 1024 * 1024;
8649
- function captureCommand(options) {
8650
- if (options.signal.aborted || options.timeoutMs <= 0) {
8651
- return Promise.resolve({
8652
- exitCode: null,
8653
- signal: null,
8654
- timedOut: !options.signal.aborted,
8655
- cancelled: options.signal.aborted,
8656
- stdout: "",
8657
- stderr: ""
8658
- });
8659
- }
8660
- return new Promise((resolve8, reject) => {
8661
- let stdout = "";
8662
- let stderr = "";
8663
- let timedOut = false;
8664
- let cancelled = false;
8665
- let settled = false;
8666
- let killTimer = null;
8667
- const child = spawn2(options.command, options.args, {
8668
- shell: false,
8669
- stdio: [
8670
- "ignore",
8671
- options.stdoutFileDescriptor === void 0 ? "pipe" : options.stdoutFileDescriptor,
8672
- "pipe"
8673
- ],
8674
- env: options.env ?? process.env,
8675
- cwd: options.cwd
8676
- });
8677
- const append = (current, chunk) => `${current}${String(chunk)}`.slice(0, MAX_OUTPUT_BYTES);
8678
- child.stdout?.on("data", (chunk) => {
8679
- stdout = append(stdout, chunk);
8680
- });
8681
- child.stderr?.on("data", (chunk) => {
8682
- stderr = append(stderr, chunk);
8683
- });
8684
- const terminate = () => {
8685
- if (child.exitCode !== null || child.killed) return;
8686
- child.kill("SIGTERM");
8687
- killTimer = setTimeout(() => {
8688
- if (child.exitCode === null) child.kill("SIGKILL");
8689
- }, 5e3);
8690
- killTimer.unref();
8691
- };
8692
- const timeout = setTimeout(() => {
8693
- timedOut = true;
8694
- terminate();
8695
- }, options.timeoutMs);
8696
- timeout.unref();
8697
- const abort = () => {
8698
- cancelled = true;
8699
- terminate();
8700
- };
8701
- options.signal.addEventListener("abort", abort, { once: true });
8702
- if (options.signal.aborted) abort();
8703
- child.once("error", (error) => {
8704
- if (settled) return;
8705
- settled = true;
8706
- clearTimeout(timeout);
8707
- if (killTimer) clearTimeout(killTimer);
8708
- options.signal.removeEventListener("abort", abort);
8709
- reject(error);
8710
- });
8711
- child.once("close", (exitCode, signal) => {
8712
- if (settled) return;
8713
- settled = true;
8714
- clearTimeout(timeout);
8715
- if (killTimer) clearTimeout(killTimer);
8716
- options.signal.removeEventListener("abort", abort);
8717
- resolve8({
8718
- exitCode,
8719
- signal,
8720
- timedOut,
8721
- cancelled,
8722
- stdout,
8723
- stderr
8724
- });
8725
- });
8726
- });
8727
- }
8728
-
8729
8821
  // src/git-code-state.ts
8730
- import { createHash as createHash3 } from "node:crypto";
8822
+ import { createHash as createHash4 } from "node:crypto";
8731
8823
  import { createReadStream as createReadStream2 } from "node:fs";
8732
8824
  import { lstat, readlink } from "node:fs/promises";
8733
8825
  import { isAbsolute as isAbsolute2, relative as relative2, resolve as resolve2, sep as sep2 } from "node:path";
@@ -8854,7 +8946,7 @@ async function worktreeCodeStateHash(folder, timeoutMs, signal) {
8854
8946
  if (head.exitCode !== 0 || untracked.exitCode !== 0 || submodules.exitCode !== 0 || operation.signal.aborted) {
8855
8947
  return null;
8856
8948
  }
8857
- const hash = createHash3("sha256");
8949
+ const hash = createHash4("sha256");
8858
8950
  hash.update("head\0");
8859
8951
  hash.update(head.stdout.trim());
8860
8952
  hash.update("\0diff\0");
@@ -10209,7 +10301,7 @@ async function fileChecksum(path) {
10209
10301
  try {
10210
10302
  const information = await stat2(path);
10211
10303
  if (!information.isFile()) return null;
10212
- const hash = createHash4("sha256");
10304
+ const hash = createHash5("sha256");
10213
10305
  await pipeline(createReadStream3(path), hash);
10214
10306
  return hash.digest("hex");
10215
10307
  } catch {
@@ -11563,7 +11655,7 @@ var importCoverageReport = async (payloadValue, timeoutMs, signal, onLog) => {
11563
11655
  };
11564
11656
 
11565
11657
  // src/handlers/signing.ts
11566
- import { createHash as createHash5, randomUUID as randomUUID2, X509Certificate } from "node:crypto";
11658
+ import { createHash as createHash6, randomUUID as randomUUID2, X509Certificate } from "node:crypto";
11567
11659
  import {
11568
11660
  mkdir as mkdir3,
11569
11661
  mkdtemp as mkdtemp3,
@@ -11656,7 +11748,7 @@ function certificateFingerprints(value) {
11656
11748
  try {
11657
11749
  const der = Buffer.from(encoded.replace(/\s+/g, ""), "base64");
11658
11750
  if (!der.length) return [];
11659
- return [createHash5("sha1").update(der).digest("hex").toUpperCase()];
11751
+ return [createHash6("sha1").update(der).digest("hex").toUpperCase()];
11660
11752
  } catch {
11661
11753
  return [];
11662
11754
  }
@@ -11737,7 +11829,7 @@ function profileAsset(decoded, now) {
11737
11829
  const createdAt = text(decoded.raw.CreationDate);
11738
11830
  return {
11739
11831
  uuid: profile.uuid,
11740
- contentHash: createHash5("sha256").update(decoded.content).digest("hex").toUpperCase(),
11832
+ contentHash: createHash6("sha256").update(decoded.content).digest("hex").toUpperCase(),
11741
11833
  name: profile.name,
11742
11834
  profileType: profile.type,
11743
11835
  bundleId: profile.bundleId,
@@ -12031,7 +12123,7 @@ var readSigningProfile = async (payload, timeoutMs, signal) => {
12031
12123
  ...successfulProcess3,
12032
12124
  uuid,
12033
12125
  contentBase64: profile.content.toString("base64"),
12034
- sha256: createHash5("sha256").update(profile.content).digest("hex").toUpperCase()
12126
+ sha256: createHash6("sha256").update(profile.content).digest("hex").toUpperCase()
12035
12127
  };
12036
12128
  };
12037
12129
  var installSigningProfile = async (payload, timeoutMs, signal) => {
@@ -12073,7 +12165,7 @@ var importSigningIdentity = async (payload, timeoutMs, signal, _onLog, context)
12073
12165
  }
12074
12166
  const secret = await context.claimSigningSecretTransfer(input.transferId);
12075
12167
  const p12 = Buffer.from(secret.p12Base64, "base64");
12076
- const hash = createHash5("sha256").update(p12).digest("hex").toUpperCase();
12168
+ const hash = createHash6("sha256").update(p12).digest("hex").toUpperCase();
12077
12169
  if (hash !== input.sha256)
12078
12170
  throw new Error("Signing identity transfer changed");
12079
12171
  const temporaryRoot = await mkdtemp3(join5(tmpdir3(), "ade-identity-"));
@@ -16754,6 +16846,7 @@ var JobExecutor = class {
16754
16846
  controller.signal,
16755
16847
  (log) => this.client.appendLog(claimed.id, log).then(() => void 0),
16756
16848
  {
16849
+ agentId: claimed.agentId,
16757
16850
  reportWorktreeActivity: (input) => this.client.reportWorktreeActivity(input),
16758
16851
  reportBuildProgress: (input) => this.client.reportBuildProgress(input),
16759
16852
  appendBuildLogChunks: (buildId, chunks) => this.client.appendBuildLogChunks(buildId, chunks),
@@ -51097,6 +51190,7 @@ async function prepareDevelopmentAgent(options, signal, dependencies = defaultDe
51097
51190
  }
51098
51191
  async function runDevelopmentAgent(options, signal) {
51099
51192
  const config = await prepareDevelopmentAgent(options, signal);
51193
+ await reapOrphanedCommandProcesses(config.agentId, signal);
51100
51194
  await runAgent(config, signal);
51101
51195
  }
51102
51196
 
@@ -51369,7 +51463,9 @@ async function main() {
51369
51463
  controller.signal
51370
51464
  );
51371
51465
  }
51372
- return await runAgent(await loadConfig(), controller.signal);
51466
+ const config = await loadConfig();
51467
+ await reapOrphanedCommandProcesses(config.agentId, controller.signal);
51468
+ return await runAgent(config, controller.signal);
51373
51469
  } finally {
51374
51470
  for (const signal of shutdownSignals) process.off(signal, stop2);
51375
51471
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-development-environment/control-agent",
3
- "version": "0.0.78",
3
+ "version": "0.0.79",
4
4
  "description": "Control agent for the AI Development Environment",
5
5
  "license": "MIT",
6
6
  "repository": {