@ai-development-environment/control-agent 0.0.54 → 0.0.55

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 +301 -48
  2. package/package.json +5 -4
@@ -1,4 +1,6 @@
1
1
  #!/usr/bin/env node
2
+ import { createRequire } from "node:module";
3
+ const require = createRequire(import.meta.url);
2
4
  var __create = Object.create;
3
5
  var __defProp = Object.defineProperty;
4
6
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
@@ -3728,15 +3730,15 @@ var require_windows = __commonJS({
3728
3730
  }
3729
3731
  return false;
3730
3732
  }
3731
- function checkStat(stat9, path, options) {
3732
- if (!stat9.isSymbolicLink() && !stat9.isFile()) {
3733
+ function checkStat(stat10, path, options) {
3734
+ if (!stat10.isSymbolicLink() && !stat10.isFile()) {
3733
3735
  return false;
3734
3736
  }
3735
3737
  return checkPathExt(path, options);
3736
3738
  }
3737
3739
  function isexe(path, options, cb2) {
3738
- fs.stat(path, function(er2, stat9) {
3739
- cb2(er2, er2 ? false : checkStat(stat9, path, options));
3740
+ fs.stat(path, function(er2, stat10) {
3741
+ cb2(er2, er2 ? false : checkStat(stat10, path, options));
3740
3742
  });
3741
3743
  }
3742
3744
  function sync(path, options) {
@@ -3752,20 +3754,20 @@ var require_mode = __commonJS({
3752
3754
  isexe.sync = sync;
3753
3755
  var fs = __require("fs");
3754
3756
  function isexe(path, options, cb2) {
3755
- fs.stat(path, function(er2, stat9) {
3756
- cb2(er2, er2 ? false : checkStat(stat9, options));
3757
+ fs.stat(path, function(er2, stat10) {
3758
+ cb2(er2, er2 ? false : checkStat(stat10, options));
3757
3759
  });
3758
3760
  }
3759
3761
  function sync(path, options) {
3760
3762
  return checkStat(fs.statSync(path), options);
3761
3763
  }
3762
- function checkStat(stat9, options) {
3763
- return stat9.isFile() && checkMode(stat9, options);
3764
+ function checkStat(stat10, options) {
3765
+ return stat10.isFile() && checkMode(stat10, options);
3764
3766
  }
3765
- function checkMode(stat9, options) {
3766
- var mod = stat9.mode;
3767
- var uid = stat9.uid;
3768
- var gid = stat9.gid;
3767
+ function checkMode(stat10, options) {
3768
+ var mod = stat10.mode;
3769
+ var uid = stat10.uid;
3770
+ var gid = stat10.gid;
3769
3771
  var myUid = options.uid !== void 0 ? options.uid : process.getuid && process.getuid();
3770
3772
  var myGid = options.gid !== void 0 ? options.gid : process.getgid && process.getgid();
3771
3773
  var u = parseInt("100", 8);
@@ -5164,6 +5166,24 @@ var AgentGraphQLClient = class {
5164
5166
  }
5165
5167
  );
5166
5168
  }
5169
+ async diskSpaceConfiguration() {
5170
+ const data = await this.request(`query AgentDiskSpaceConfiguration {
5171
+ agentDiskSpaceConfiguration {
5172
+ enabled pollIntervalSeconds baseRepoDirectory
5173
+ derivedDataLocationMode derivedDataPath
5174
+ worktrees { id folder }
5175
+ }
5176
+ }`);
5177
+ return data.agentDiskSpaceConfiguration;
5178
+ }
5179
+ reportDiskSpace(input) {
5180
+ return this.request(
5181
+ `mutation ReportAgentDiskSpace($input: AgentDiskSpaceReportInput!) {
5182
+ reportAgentDiskSpace(input: $input)
5183
+ }`,
5184
+ { input }
5185
+ );
5186
+ }
5167
5187
  async pendingJobs(agentId) {
5168
5188
  const data = await this.request(
5169
5189
  `query PendingJobs($agentId: ID!) {
@@ -5592,6 +5612,10 @@ function nullableString(value, name) {
5592
5612
  if (value === null) return null;
5593
5613
  return stringValue2(value, name);
5594
5614
  }
5615
+ function optionalNullableString(value, name) {
5616
+ if (value === void 0 || value === null) return null;
5617
+ return stringValue2(value, name);
5618
+ }
5595
5619
  function exactKeys(value, allowed, name) {
5596
5620
  const unexpected = Object.keys(value).find((key) => !allowed.includes(key));
5597
5621
  if (unexpected) throw new Error(`Unexpected ${name} field: ${unexpected}`);
@@ -5621,6 +5645,44 @@ function target(value, index) {
5621
5645
  )
5622
5646
  };
5623
5647
  }
5648
+ function deleteTarget(value, index) {
5649
+ const item = objectValue2(value, `build data delete targets[${index}]`);
5650
+ exactKeys(
5651
+ item,
5652
+ ["path", "rootPath", "name", "kind", "worktreeId", "worktreePath"],
5653
+ "build data delete target"
5654
+ );
5655
+ const kind = item.kind;
5656
+ if (kind !== void 0 && !["PROJECT", "PENDING", "SHARED_CACHE", "DEVICE_SUPPORT"].includes(kind)) {
5657
+ throw new Error(`build data delete targets[${index}].kind is invalid`);
5658
+ }
5659
+ return {
5660
+ path: stringValue2(item.path, `build data delete targets[${index}].path`),
5661
+ rootPath: stringValue2(
5662
+ item.rootPath,
5663
+ `build data delete targets[${index}].rootPath`
5664
+ ),
5665
+ ...item.name === void 0 ? {} : {
5666
+ name: stringValue2(
5667
+ item.name,
5668
+ `build data delete targets[${index}].name`
5669
+ )
5670
+ },
5671
+ ...kind === void 0 ? {} : { kind },
5672
+ ...item.worktreeId === void 0 ? {} : {
5673
+ worktreeId: optionalNullableString(
5674
+ item.worktreeId,
5675
+ `build data delete targets[${index}].worktreeId`
5676
+ )
5677
+ },
5678
+ ...item.worktreePath === void 0 ? {} : {
5679
+ worktreePath: optionalNullableString(
5680
+ item.worktreePath,
5681
+ `build data delete targets[${index}].worktreePath`
5682
+ )
5683
+ }
5684
+ };
5685
+ }
5624
5686
  function buildDataScanPayload(value) {
5625
5687
  const payload = objectValue2(value, "build data scan payload");
5626
5688
  exactKeys(payload, ["mode", "path", "worktrees"], "build data scan payload");
@@ -5651,6 +5713,23 @@ function buildDataTargetsPayload(value) {
5651
5713
  }
5652
5714
  return { targets: payload.targets.map(target) };
5653
5715
  }
5716
+ function buildDataDeletePayload(value) {
5717
+ const payload = objectValue2(value, "build data delete payload");
5718
+ exactKeys(payload, ["targets", "source"], "build data delete payload");
5719
+ if (!Array.isArray(payload.targets) || payload.targets.length > 1e4) {
5720
+ throw new Error(
5721
+ "build data delete targets must be an array of at most 10,000 items"
5722
+ );
5723
+ }
5724
+ const source = payload.source ?? "USER";
5725
+ if (source !== "USER" && source !== "AUTOMATIC") {
5726
+ throw new Error("build data delete source is invalid");
5727
+ }
5728
+ return {
5729
+ targets: payload.targets.map(deleteTarget),
5730
+ source
5731
+ };
5732
+ }
5654
5733
 
5655
5734
  // ../agent-contract/src/codebases.ts
5656
5735
  var CODEBASE_BROWSE_JOB_KIND = "codebase.browse";
@@ -8987,7 +9066,15 @@ async function runCcusage(payload, timeoutMs, signal, onLog) {
8987
9066
  }
8988
9067
 
8989
9068
  // src/handlers/build-data.ts
8990
- import { lstat, opendir, readdir as readdir2, realpath as realpath2, rm as rm4 } from "node:fs/promises";
9069
+ import {
9070
+ lstat,
9071
+ opendir,
9072
+ readdir as readdir2,
9073
+ realpath as realpath2,
9074
+ rm as rm4,
9075
+ stat as stat3,
9076
+ statfs
9077
+ } from "node:fs/promises";
8991
9078
  import { homedir as homedir4 } from "node:os";
8992
9079
  import { basename as basename2, dirname as dirname2, isAbsolute, join as join4, resolve } from "node:path";
8993
9080
  var successfulProcess2 = {
@@ -9063,10 +9150,13 @@ async function scanRoot(configuredRoot, timeoutMs, signal) {
9063
9150
  };
9064
9151
  }
9065
9152
  const entries = [];
9153
+ const rootStats = await stat3(rootPath2);
9154
+ const volumeId = String(rootStats.dev);
9066
9155
  for (const child of children) {
9067
9156
  signal.throwIfAborted();
9068
9157
  if (!child.isDirectory()) continue;
9069
9158
  const path = join4(rootPath2, child.name);
9159
+ const information = await lstat(path);
9070
9160
  const projectPath = await workspacePath(
9071
9161
  join4(path, "info.plist"),
9072
9162
  timeoutMs,
@@ -9077,7 +9167,9 @@ async function scanRoot(configuredRoot, timeoutMs, signal) {
9077
9167
  rootPath: rootPath2,
9078
9168
  name: child.name,
9079
9169
  kind: projectPath ? "PROJECT" : child.name.toLocaleLowerCase().endsWith(".noindex") ? "SHARED_CACHE" : "PENDING",
9080
- workspacePath: projectPath
9170
+ workspacePath: projectPath,
9171
+ modifiedAt: information.mtime.toISOString(),
9172
+ volumeId
9081
9173
  });
9082
9174
  }
9083
9175
  entries.sort((first, second) => first.name.localeCompare(second.name));
@@ -9109,7 +9201,9 @@ async function scanDeviceSupport(signal) {
9109
9201
  rootPath: rootPath2,
9110
9202
  name: child.name,
9111
9203
  kind: "DEVICE_SUPPORT",
9112
- workspacePath: null
9204
+ workspacePath: null,
9205
+ modifiedAt: null,
9206
+ volumeId: null
9113
9207
  })).sort((first, second) => first.name.localeCompare(second.name)),
9114
9208
  warning: null
9115
9209
  };
@@ -9203,7 +9297,7 @@ var sizeBuildData = async (rawPayload, _timeoutMs, signal) => {
9203
9297
  return { ...successfulProcess2, sizes };
9204
9298
  };
9205
9299
  var deleteBuildData = async (rawPayload, _timeoutMs, signal) => {
9206
- const payload = buildDataTargetsPayload(rawPayload);
9300
+ const payload = buildDataDeletePayload(rawPayload);
9207
9301
  const deleted = [];
9208
9302
  for (const target2 of payload.targets) {
9209
9303
  signal.throwIfAborted();
@@ -9230,9 +9324,120 @@ var deleteBuildData = async (rawPayload, _timeoutMs, signal) => {
9230
9324
  }
9231
9325
  return { ...successfulProcess2, deleted };
9232
9326
  };
9327
+ async function sharedCapacityId(canonicalPath, volumeId, signal) {
9328
+ try {
9329
+ const result = await captureCommand({
9330
+ command: "/usr/sbin/diskutil",
9331
+ args: ["info", "-plist", canonicalPath],
9332
+ timeoutMs: 5e3,
9333
+ signal
9334
+ });
9335
+ signal.throwIfAborted();
9336
+ if (result.exitCode !== 0) return volumeId;
9337
+ const information = parsePlist(result.stdout);
9338
+ if (!information || typeof information !== "object") return volumeId;
9339
+ const container = information.APFSContainerReference;
9340
+ return typeof container === "string" && container.trim() ? `apfs:${container.trim()}` : volumeId;
9341
+ } catch {
9342
+ signal.throwIfAborted();
9343
+ return volumeId;
9344
+ }
9345
+ }
9346
+ async function inspectVolume(configuredPath, role, signal) {
9347
+ signal.throwIfAborted();
9348
+ const canonicalPath = await realpath2(configuredPath);
9349
+ const [information, filesystem] = await Promise.all([
9350
+ stat3(canonicalPath),
9351
+ statfs(canonicalPath, { bigint: true })
9352
+ ]);
9353
+ const id2 = String(information.dev);
9354
+ return {
9355
+ canonicalPath,
9356
+ volume: {
9357
+ id: id2,
9358
+ capacityId: id2,
9359
+ totalBytes: Number(filesystem.blocks * filesystem.bsize),
9360
+ freeBytes: Number(filesystem.bavail * filesystem.bsize),
9361
+ roles: /* @__PURE__ */ new Set([role]),
9362
+ paths: /* @__PURE__ */ new Set([canonicalPath])
9363
+ }
9364
+ };
9365
+ }
9366
+ async function collectAgentDiskSpace(configuration, signal) {
9367
+ const scanPayload = buildDataScanPayload({
9368
+ mode: configuration.derivedDataLocationMode,
9369
+ path: configuration.derivedDataPath,
9370
+ worktrees: configuration.worktrees
9371
+ });
9372
+ const roots = await configuredRoots(scanPayload);
9373
+ const requested = [
9374
+ { path: "/", role: "MAIN" },
9375
+ ...configuration.baseRepoDirectory ? [{ path: configuration.baseRepoDirectory, role: "BASE_REPO" }] : [],
9376
+ ...roots.map((path) => ({ path, role: "DERIVED_DATA" }))
9377
+ ];
9378
+ const warnings = [];
9379
+ const volumes = /* @__PURE__ */ new Map();
9380
+ for (const request of requested) {
9381
+ signal.throwIfAborted();
9382
+ try {
9383
+ const inspected = await inspectVolume(request.path, request.role, signal);
9384
+ const existing = volumes.get(inspected.volume.id);
9385
+ if (existing) {
9386
+ existing.roles.add(request.role);
9387
+ existing.paths.add(inspected.canonicalPath);
9388
+ existing.totalBytes = inspected.volume.totalBytes;
9389
+ existing.freeBytes = inspected.volume.freeBytes;
9390
+ } else {
9391
+ inspected.volume.capacityId = await sharedCapacityId(
9392
+ inspected.canonicalPath,
9393
+ inspected.volume.id,
9394
+ signal
9395
+ );
9396
+ volumes.set(inspected.volume.id, inspected.volume);
9397
+ }
9398
+ } catch (error) {
9399
+ warnings.push(`${request.path}: ${errorMessage(error)}`);
9400
+ }
9401
+ }
9402
+ const entries = [];
9403
+ const scannedRoots = /* @__PURE__ */ new Set();
9404
+ for (const root of roots) {
9405
+ signal.throwIfAborted();
9406
+ let canonical = root;
9407
+ try {
9408
+ canonical = await realpath2(root);
9409
+ } catch {
9410
+ }
9411
+ if (scannedRoots.has(canonical)) continue;
9412
+ scannedRoots.add(canonical);
9413
+ const result = await scanRoot(root, 45e3, signal);
9414
+ if (result.warning) warnings.push(result.warning);
9415
+ for (const entry of result.entries) {
9416
+ if (!entry.modifiedAt || !entry.volumeId) continue;
9417
+ entries.push({
9418
+ ...entry,
9419
+ modifiedAt: entry.modifiedAt,
9420
+ volumeId: entry.volumeId
9421
+ });
9422
+ }
9423
+ }
9424
+ return {
9425
+ observedAt: (/* @__PURE__ */ new Date()).toISOString(),
9426
+ volumes: [...volumes.values()].map((volume) => ({
9427
+ id: volume.id,
9428
+ capacityId: volume.capacityId,
9429
+ totalBytes: volume.totalBytes,
9430
+ freeBytes: volume.freeBytes,
9431
+ roles: [...volume.roles],
9432
+ paths: [...volume.paths]
9433
+ })),
9434
+ entries,
9435
+ warnings: [...new Set(warnings)]
9436
+ };
9437
+ }
9233
9438
 
9234
9439
  // src/handlers/codebases.ts
9235
- import { readdir as readdir3, realpath as realpath3, stat as stat3 } from "node:fs/promises";
9440
+ import { readdir as readdir3, realpath as realpath3, stat as stat4 } from "node:fs/promises";
9236
9441
  import { homedir as homedir5 } from "node:os";
9237
9442
  import { dirname as dirname3, join as join5, resolve as resolve2 } from "node:path";
9238
9443
  var successfulProcess3 = {
@@ -9311,7 +9516,7 @@ function baseSnapshot(folder) {
9311
9516
  }
9312
9517
  async function fetchedAt(commonDirectory) {
9313
9518
  try {
9314
- return (await stat3(join5(commonDirectory, "FETCH_HEAD"))).mtime.toISOString();
9519
+ return (await stat4(join5(commonDirectory, "FETCH_HEAD"))).mtime.toISOString();
9315
9520
  } catch {
9316
9521
  return null;
9317
9522
  }
@@ -9322,7 +9527,7 @@ async function inspectCodebaseProcess(selectedFolder, timeoutMs, signal, expecte
9322
9527
  let selected;
9323
9528
  try {
9324
9529
  selected = await realpath3(fallbackFolder);
9325
- if (!(await stat3(selected)).isDirectory())
9530
+ if (!(await stat4(selected)).isDirectory())
9326
9531
  throw new Error("Folder is not a directory");
9327
9532
  } catch (error) {
9328
9533
  return {
@@ -9492,7 +9697,7 @@ var browseCodebaseDirectories = async (payload) => {
9492
9697
  const input = codebaseBrowsePayload(payload);
9493
9698
  const homePath = await realpath3(homedir5());
9494
9699
  const path = await realpath3(input.path ? resolve2(input.path) : homePath);
9495
- if (!(await stat3(path)).isDirectory())
9700
+ if (!(await stat4(path)).isDirectory())
9496
9701
  throw new Error("Path is not a directory");
9497
9702
  const candidates = await readdir3(path, { withFileTypes: true });
9498
9703
  const entries = [];
@@ -9506,7 +9711,7 @@ var browseCodebaseDirectories = async (payload) => {
9506
9711
  } else if (candidate.isSymbolicLink()) {
9507
9712
  try {
9508
9713
  const target2 = join5(path, candidate.name);
9509
- if ((await stat3(target2)).isDirectory()) {
9714
+ if ((await stat4(target2)).isDirectory()) {
9510
9715
  entries.push({
9511
9716
  name: candidate.name,
9512
9717
  path: target2,
@@ -10115,7 +10320,7 @@ import {
10115
10320
  readdir as readdir4,
10116
10321
  rename as rename2,
10117
10322
  rm as rm5,
10118
- stat as stat4,
10323
+ stat as stat5,
10119
10324
  writeFile as writeFile4
10120
10325
  } from "node:fs/promises";
10121
10326
  import { homedir as homedir6 } from "node:os";
@@ -10174,7 +10379,7 @@ var PROJECT_ROOTS = {
10174
10379
  };
10175
10380
  async function directoryExists(path) {
10176
10381
  try {
10177
- return (await stat4(path)).isDirectory();
10382
+ return (await stat5(path)).isDirectory();
10178
10383
  } catch {
10179
10384
  return false;
10180
10385
  }
@@ -10508,7 +10713,7 @@ var applySkills = async (payload) => {
10508
10713
  };
10509
10714
 
10510
10715
  // src/handlers/runs.ts
10511
- import { readFile as readFile4, readdir as readdir5, stat as stat5 } from "node:fs/promises";
10716
+ import { readFile as readFile4, readdir as readdir5, stat as stat6 } from "node:fs/promises";
10512
10717
  import { homedir as homedir7 } from "node:os";
10513
10718
  import { join as join7 } from "node:path";
10514
10719
  var successfulProcess5 = {
@@ -10519,7 +10724,7 @@ var successfulProcess5 = {
10519
10724
  };
10520
10725
  async function fileExists(path) {
10521
10726
  try {
10522
- return (await stat5(path)).isFile();
10727
+ return (await stat6(path)).isFile();
10523
10728
  } catch {
10524
10729
  return false;
10525
10730
  }
@@ -10574,7 +10779,7 @@ var readRunSession = async (payload) => {
10574
10779
  `No ${input.provider === "CLAUDE" ? "Claude" : "Codex"} session file found for this run on this agent`
10575
10780
  );
10576
10781
  }
10577
- const { size } = await stat5(path);
10782
+ const { size } = await stat6(path);
10578
10783
  if (size > MAX_RUN_SESSION_FILE_BYTES) {
10579
10784
  throw new Error(
10580
10785
  `Session file is too large to export (${size} bytes exceeds the ${MAX_RUN_SESSION_FILE_BYTES} byte limit)`
@@ -10594,7 +10799,7 @@ import {
10594
10799
  readFile as readFile6,
10595
10800
  realpath as realpath6,
10596
10801
  rm as rm8,
10597
- stat as stat7,
10802
+ stat as stat8,
10598
10803
  writeFile as writeFile6
10599
10804
  } from "node:fs/promises";
10600
10805
  import { tmpdir as tmpdir5 } from "node:os";
@@ -10792,7 +10997,7 @@ import {
10792
10997
  realpath as realpath5,
10793
10998
  rename as rename3,
10794
10999
  rm as rm7,
10795
- stat as stat6,
11000
+ stat as stat7,
10796
11001
  writeFile as writeFile5
10797
11002
  } from "node:fs/promises";
10798
11003
  import { createReadStream as createReadStream3, createWriteStream } from "node:fs";
@@ -11058,7 +11263,7 @@ function requireSuccess2(result, fallback) {
11058
11263
  }
11059
11264
  async function validateWorktree(input, timeoutMs, signal) {
11060
11265
  const folder = await realpath5(input.folder);
11061
- if (!(await stat6(folder)).isDirectory())
11266
+ if (!(await stat7(folder)).isDirectory())
11062
11267
  throw new Error("Worktree is missing");
11063
11268
  const gitDirectory2 = requireSuccess2(
11064
11269
  await command(
@@ -11105,7 +11310,7 @@ async function validateSource(root, source) {
11105
11310
  if (difference === ".." || difference.startsWith(`..${sep4}`) || isAbsolute4(difference)) {
11106
11311
  throw new Error("Build source resolves outside the worktree");
11107
11312
  }
11108
- const information = await stat6(resolved);
11313
+ const information = await stat7(resolved);
11109
11314
  if (source.kind === "PACKAGE" && !information.isFile()) {
11110
11315
  throw new Error("Package.swift is missing");
11111
11316
  }
@@ -11221,7 +11426,7 @@ async function workspaceProjectPaths(workspacePath2, folder) {
11221
11426
  try {
11222
11427
  const resolved = await realpath5(candidate);
11223
11428
  const resolvedDifference = relative4(folder, resolved);
11224
- if (resolvedDifference === ".." || resolvedDifference.startsWith(`..${sep4}`) || isAbsolute4(resolvedDifference) || !(await stat6(resolved)).isDirectory()) {
11429
+ if (resolvedDifference === ".." || resolvedDifference.startsWith(`..${sep4}`) || isAbsolute4(resolvedDifference) || !(await stat7(resolved)).isDirectory()) {
11225
11430
  continue;
11226
11431
  }
11227
11432
  projects.push(resolved);
@@ -12272,14 +12477,14 @@ if (typeof hookModule.default === "function") {
12272
12477
  }
12273
12478
  async function pathExists(path) {
12274
12479
  try {
12275
- await stat6(path);
12480
+ await stat7(path);
12276
12481
  return true;
12277
12482
  } catch {
12278
12483
  return false;
12279
12484
  }
12280
12485
  }
12281
12486
  async function pathSize(path) {
12282
- const information = await stat6(path);
12487
+ const information = await stat7(path);
12283
12488
  if (information.isFile()) return information.size;
12284
12489
  if (!information.isDirectory()) return 0;
12285
12490
  let size = 0;
@@ -12291,7 +12496,7 @@ async function pathSize(path) {
12291
12496
  }
12292
12497
  async function fileChecksum(path) {
12293
12498
  try {
12294
- const information = await stat6(path);
12499
+ const information = await stat7(path);
12295
12500
  if (!information.isFile()) return null;
12296
12501
  const hash = createHash5("sha256");
12297
12502
  await pipeline(createReadStream3(path), hash);
@@ -12875,10 +13080,10 @@ var runIosBuild = async (payload, timeoutMs, signal, onLog, context) => {
12875
13080
  const testProductsPath = input.advancedSettings.priorTestProductsPath;
12876
13081
  const xctestrunPath = input.advancedSettings.priorXctestrunPath;
12877
13082
  if (testProductsPath) {
12878
- if (!testProductsPath.endsWith(".xctestproducts") || !await pathExists(testProductsPath) || !(await stat6(testProductsPath)).isDirectory()) {
13083
+ if (!testProductsPath.endsWith(".xctestproducts") || !await pathExists(testProductsPath) || !(await stat7(testProductsPath)).isDirectory()) {
12879
13084
  throw new Error("The captured test-products artifact is unavailable");
12880
13085
  }
12881
- } else if (!xctestrunPath?.endsWith(".xctestrun") || !await pathExists(xctestrunPath) || !(await stat6(xctestrunPath)).isFile()) {
13086
+ } else if (!xctestrunPath?.endsWith(".xctestrun") || !await pathExists(xctestrunPath) || !(await stat7(xctestrunPath)).isFile()) {
12882
13087
  throw new Error("The captured .xctestrun artifact is unavailable");
12883
13088
  }
12884
13089
  }
@@ -13176,7 +13381,7 @@ var downloadIosBuildArtifact = async (payload, timeoutMs, signal, onLog, context
13176
13381
  if (!difference || difference === ".." || difference.startsWith(`..${sep4}`) || isAbsolute4(difference)) {
13177
13382
  throw new Error("Build artifact resolves outside the build folder");
13178
13383
  }
13179
- const information = await stat6(target2);
13384
+ const information = await stat7(target2);
13180
13385
  let uploadPath = target2;
13181
13386
  let filename = basename3(target2);
13182
13387
  let contentType = "application/octet-stream";
@@ -13560,7 +13765,7 @@ var success = {
13560
13765
  var runWorkflowTerminal = async (payloadValue, timeoutMs, signal, onLog, context) => {
13561
13766
  const payload = parseWorkflowTerminalPayload(payloadValue);
13562
13767
  const cwd = await realpath6(payload.cwd);
13563
- const information = await stat7(cwd);
13768
+ const information = await stat8(cwd);
13564
13769
  if (!information.isDirectory())
13565
13770
  throw new Error("Workflow working directory is not a directory");
13566
13771
  const directory = await mkdtemp5(join10(tmpdir5(), "aide-workflow-"));
@@ -13657,7 +13862,7 @@ var runWorkflowGitCheckpoint = async (payloadValue, _timeoutMs, signal) => {
13657
13862
 
13658
13863
  // src/handlers/worktrees.ts
13659
13864
  import { createWriteStream as createWriteStream2, watch } from "node:fs";
13660
- import { mkdtemp as mkdtemp6, open as open2, readFile as readFile7, realpath as realpath7, rm as rm9, stat as stat8 } from "node:fs/promises";
13865
+ import { mkdtemp as mkdtemp6, open as open2, readFile as readFile7, realpath as realpath7, rm as rm9, stat as stat9 } from "node:fs/promises";
13661
13866
  import { tmpdir as tmpdir6 } from "node:os";
13662
13867
  import { basename as basename4, dirname as dirname6, isAbsolute as isAbsolute5, join as join11, relative as relative5 } from "node:path";
13663
13868
  import { spawn as spawn6 } from "node:child_process";
@@ -13714,7 +13919,7 @@ async function rebaseInProgress(folder, timeoutMs, signal) {
13714
13919
  const candidate = pathResult.stdout.trim();
13715
13920
  if (!candidate) continue;
13716
13921
  try {
13717
- await stat8(isAbsolute5(candidate) ? candidate : join11(folder, candidate));
13922
+ await stat9(isAbsolute5(candidate) ? candidate : join11(folder, candidate));
13718
13923
  return true;
13719
13924
  } catch {
13720
13925
  }
@@ -13928,7 +14133,7 @@ async function origin(folder, timeoutMs, signal) {
13928
14133
  }
13929
14134
  async function validateWorktree2(input, timeoutMs, signal) {
13930
14135
  const folder = await realpath7(input.folder);
13931
- if (!(await stat8(folder)).isDirectory())
14136
+ if (!(await stat9(folder)).isDirectory())
13932
14137
  throw new Error("Worktree is missing");
13933
14138
  const observedGitDirectory = await gitDirectory(folder, timeoutMs, signal);
13934
14139
  if (observedGitDirectory !== input.gitDirectory) {
@@ -14169,7 +14374,7 @@ function parseNumstat(value) {
14169
14374
  }
14170
14375
  async function untrackedLines(folder, path) {
14171
14376
  try {
14172
- const file = await stat8(`${folder}/${path}`);
14377
+ const file = await stat9(`${folder}/${path}`);
14173
14378
  if (!file.isFile() || file.size > 1024 * 1024) return null;
14174
14379
  const contents = await readFile7(`${folder}/${path}`);
14175
14380
  if (contents.includes(0)) return null;
@@ -14465,7 +14670,7 @@ async function availableSide(side, folder, timeoutMs, signal) {
14465
14670
  try {
14466
14671
  const resolved = await realpath7(side.path);
14467
14672
  const difference = relative5(folder, resolved);
14468
- return difference !== ".." && !difference.startsWith("../") && (await stat8(resolved)).isFile();
14673
+ return difference !== ".." && !difference.startsWith("../") && (await stat9(resolved)).isFile();
14469
14674
  } catch {
14470
14675
  return false;
14471
14676
  }
@@ -14734,7 +14939,7 @@ var downloadWorktreeDiffAsset = async (payload, timeoutMs, signal, _onLog, conte
14734
14939
  signal
14735
14940
  );
14736
14941
  }
14737
- const information = await stat8(uploadPath);
14942
+ const information = await stat9(uploadPath);
14738
14943
  if (!information.isFile() || information.size > 20 * 1024 * 1024) {
14739
14944
  throw new Error("Diff image exceeds the 20 MiB limit");
14740
14945
  }
@@ -14925,7 +15130,7 @@ async function chooseNewBranch(folder, candidates, allowedCurrentBranch, timeout
14925
15130
  }
14926
15131
  async function validateBranchRoot(input, timeoutMs, signal) {
14927
15132
  const rootFolder = await realpath7(input.rootFolder);
14928
- if (!(await stat8(rootFolder)).isDirectory()) {
15133
+ if (!(await stat9(rootFolder)).isDirectory()) {
14929
15134
  throw new Error("Base repository is missing");
14930
15135
  }
14931
15136
  if (await origin(rootFolder, timeoutMs, signal) !== input.expectedOrigin) {
@@ -14994,7 +15199,7 @@ var branchWorktree = async (payload, timeoutMs, signal) => {
14994
15199
  ) : changeFolder;
14995
15200
  if (input.action === "CREATE") {
14996
15201
  try {
14997
- await stat8(targetFolder);
15202
+ await stat9(targetFolder);
14998
15203
  throw new Error(`Worktree folder already exists: ${targetFolder}`);
14999
15204
  } catch (error) {
15000
15205
  if (!(error instanceof Error) || !("code" in error) || error.code !== "ENOENT") {
@@ -15233,7 +15438,7 @@ var checkoutMovedWorktree = async (payload, timeoutMs, signal) => {
15233
15438
  `${basename4(rootFolder)}-${input.branch.replaceAll("/", "-")}`
15234
15439
  );
15235
15440
  try {
15236
- await stat8(targetFolder);
15441
+ await stat9(targetFolder);
15237
15442
  throw new Error(`Worktree folder already exists: ${targetFolder}`);
15238
15443
  } catch (error) {
15239
15444
  if (!(error instanceof Error) || !("code" in error) || error.code !== "ENOENT") {
@@ -15394,7 +15599,7 @@ var checkoutMovedWorktree = async (payload, timeoutMs, signal) => {
15394
15599
  var deleteWorktree = async (payload, timeoutMs, signal) => {
15395
15600
  const input = worktreeDeleteJobPayload(payload);
15396
15601
  const rootFolder = await realpath7(input.rootFolder);
15397
- if (!(await stat8(rootFolder)).isDirectory()) {
15602
+ if (!(await stat9(rootFolder)).isDirectory()) {
15398
15603
  throw new Error("Base repository is missing");
15399
15604
  }
15400
15605
  if (await origin(rootFolder, timeoutMs, signal) !== input.expectedOrigin) {
@@ -49596,6 +49801,10 @@ var RunManager = class {
49596
49801
  }
49597
49802
  };
49598
49803
 
49804
+ // ../agent-contract/src/disk-space.ts
49805
+ var DISK_SPACE_POLL_INTERVAL_SECONDS = 60;
49806
+ var DISK_SPACE_STALE_AFTER_SECONDS = DISK_SPACE_POLL_INTERVAL_SECONDS * 2;
49807
+
49599
49808
  // src/agent-runtime.ts
49600
49809
  function configuredIntervalMs(value, fallbackSeconds, minSeconds, maxSeconds) {
49601
49810
  return (Number.isInteger(value) && value >= minSeconds && value <= maxSeconds ? value : fallbackSeconds) * 1e3;
@@ -49618,6 +49827,9 @@ async function runAgent(config, signal) {
49618
49827
  let heartbeatTimer;
49619
49828
  let jobReconciliationTimer;
49620
49829
  let codebaseTimer;
49830
+ let diskSpaceTimer;
49831
+ let diskSpaceRunning = false;
49832
+ let diskSpaceIntervalMs = DISK_SPACE_POLL_INTERVAL_SECONDS * 1e3;
49621
49833
  let startupRecoveryPending = true;
49622
49834
  const interruptedJobs = /* @__PURE__ */ new Set();
49623
49835
  const reconcileJobs = async (recoverInterrupted) => {
@@ -49771,11 +49983,48 @@ async function runAgent(config, signal) {
49771
49983
  codebaseTimer = void 0;
49772
49984
  void reconcileCodebases();
49773
49985
  };
49986
+ const scheduleDiskSpacePoll = () => {
49987
+ if (diskSpaceTimer) clearTimeout(diskSpaceTimer);
49988
+ if (!signal.aborted) {
49989
+ diskSpaceTimer = setTimeout(
49990
+ () => void runDiskSpacePoll(),
49991
+ diskSpaceIntervalMs
49992
+ );
49993
+ }
49994
+ };
49995
+ const runDiskSpacePoll = async () => {
49996
+ if (diskSpaceRunning || signal.aborted) return;
49997
+ diskSpaceRunning = true;
49998
+ if (diskSpaceTimer) clearTimeout(diskSpaceTimer);
49999
+ diskSpaceTimer = void 0;
50000
+ try {
50001
+ const configuration = await client2.diskSpaceConfiguration();
50002
+ diskSpaceIntervalMs = Math.max(
50003
+ 1e3,
50004
+ configuration.pollIntervalSeconds * 1e3
50005
+ );
50006
+ if (configuration.enabled) {
50007
+ const report = await collectAgentDiskSpace(configuration, signal);
50008
+ await client2.reportDiskSpace(report);
50009
+ }
50010
+ } catch (error) {
50011
+ if (!signal.aborted) {
50012
+ console.error(
50013
+ "Disk space poll failed:",
50014
+ error instanceof Error ? error.message : error
50015
+ );
50016
+ }
50017
+ } finally {
50018
+ diskSpaceRunning = false;
50019
+ scheduleDiskSpacePoll();
50020
+ }
50021
+ };
49774
50022
  await refreshCadence();
49775
50023
  await heartbeat();
49776
50024
  await runJobReconciliation();
49777
50025
  await runManager.start();
49778
50026
  void reconcileCodebases();
50027
+ void runDiskSpacePoll();
49779
50028
  const subscriptionClient = createAgentSubscriptionClient(config);
49780
50029
  const unsubscribe = subscribeToAgentEvents(
49781
50030
  subscriptionClient,
@@ -49788,11 +50037,14 @@ async function runAgent(config, signal) {
49788
50037
  requestCodebaseReconcile();
49789
50038
  } else if (event.type === "RUN_COMMAND_AVAILABLE") {
49790
50039
  runManager.execute(event.runCommand);
50040
+ } else if (event.type === "DISK_SPACE_POLL_REQUESTED") {
50041
+ void runDiskSpacePoll();
49791
50042
  } else {
49792
50043
  void refreshCadence().then(() => {
49793
50044
  scheduleHeartbeat();
49794
50045
  scheduleJobReconciliation();
49795
50046
  requestCodebaseReconcile();
50047
+ void runDiskSpacePoll();
49796
50048
  });
49797
50049
  }
49798
50050
  }
@@ -49804,6 +50056,7 @@ async function runAgent(config, signal) {
49804
50056
  if (heartbeatTimer) clearTimeout(heartbeatTimer);
49805
50057
  if (jobReconciliationTimer) clearTimeout(jobReconciliationTimer);
49806
50058
  if (codebaseTimer) clearTimeout(codebaseTimer);
50059
+ if (diskSpaceTimer) clearTimeout(diskSpaceTimer);
49807
50060
  unsubscribe();
49808
50061
  await subscriptionClient.dispose();
49809
50062
  await executor.cancelAll();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-development-environment/control-agent",
3
- "version": "0.0.54",
3
+ "version": "0.0.55",
4
4
  "description": "Control agent for the AI Development Environment",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -28,7 +28,8 @@
28
28
  "ws": "8.21.1"
29
29
  },
30
30
  "scripts": {
31
- "build": "esbuild src/index.ts --bundle --platform=node --format=esm --target=node24 --outfile=dist/control-agent.js --banner:js=\"#!/usr/bin/env node\"",
31
+ "build": "node esbuild.config.mjs",
32
+ "smoke": "node dist/control-agent.js --help",
32
33
  "prepublishOnly": "npm run build",
33
34
  "dev": "node --watch --watch-preserve-output --import tsx src/index.ts dev",
34
35
  "format": "prettier --write --ignore-path ../../.gitignore --ignore-path ../../.prettierignore .",
@@ -38,8 +39,8 @@
38
39
  "type-check": "tsc --noEmit -p tsconfig.json",
39
40
  "test": "vitest",
40
41
  "test:run": "vitest run",
41
- "full-check": "npm run format && npm run lint:fix && npm run type-check && npm run test:run && npm run build",
42
- "full-check:ci": "npm run format:check && npm run lint && npm run type-check && npm run test:run && npm run build",
42
+ "full-check": "npm run format && npm run lint:fix && npm run type-check && npm run test:run && npm run build && npm run smoke",
43
+ "full-check:ci": "npm run format:check && npm run lint && npm run type-check && npm run test:run && npm run build && npm run smoke",
43
44
  "full-check:dev": "npm run full-check && npm run dev",
44
45
  "upgrade": "ncu -u --cooldown 48h && npm install",
45
46
  "upgrade:check": "ncu --cooldown 48h"