@ai-development-environment/control-agent 0.0.27 → 0.0.29

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 +290 -82
  2. package/package.json +1 -1
@@ -269,8 +269,8 @@ function createClient(options) {
269
269
  retryWait = async function randomisedExponentialBackoff(retries2) {
270
270
  const retryDelaySeconds = Math.pow(2, retries2);
271
271
  await new Promise(
272
- (resolve5) => setTimeout(
273
- resolve5,
272
+ (resolve6) => setTimeout(
273
+ resolve6,
274
274
  retryDelaySeconds * 1e3 + // add random timeout from 300ms to 3s
275
275
  Math.floor(Math.random() * (3e3 - 300) + 300)
276
276
  )
@@ -506,7 +506,7 @@ function createClient(options) {
506
506
  if (socket.readyState === WebSocketImpl.CLOSING) await throwOnClose;
507
507
  let release2 = () => {
508
508
  };
509
- const released = new Promise((resolve5) => release2 = resolve5);
509
+ const released = new Promise((resolve6) => release2 = resolve6);
510
510
  return [
511
511
  socket,
512
512
  release2,
@@ -669,7 +669,7 @@ function createClient(options) {
669
669
  const iterator = (async function* iterator2() {
670
670
  for (; ; ) {
671
671
  if (!pending.length) {
672
- await new Promise((resolve5) => deferred.resolve = resolve5);
672
+ await new Promise((resolve6) => deferred.resolve = resolve6);
673
673
  }
674
674
  while (pending.length) {
675
675
  yield pending.shift();
@@ -960,7 +960,7 @@ function createAgentSubscriptionClient(config) {
960
960
  shouldRetry: () => true,
961
961
  retryWait: async (retries) => {
962
962
  const delay = Math.min(3e4, 1e3 * 2 ** Math.min(retries, 5));
963
- await new Promise((resolve5) => setTimeout(resolve5, delay));
963
+ await new Promise((resolve6) => setTimeout(resolve6, delay));
964
964
  },
965
965
  on: {
966
966
  connected: () => console.log("Connected to control-plane WebSocket"),
@@ -2756,7 +2756,7 @@ function collectInventory() {
2756
2756
  // src/process-runner.ts
2757
2757
  import { spawn } from "node:child_process";
2758
2758
  async function runProcess(options) {
2759
- return new Promise((resolve5, reject) => {
2759
+ return new Promise((resolve6, reject) => {
2760
2760
  let sequence = 0;
2761
2761
  let timedOut = false;
2762
2762
  let cancelled = false;
@@ -2833,7 +2833,7 @@ async function runProcess(options) {
2833
2833
  clearTimeout(timeout);
2834
2834
  options.signal.removeEventListener("abort", abort);
2835
2835
  void logChain.then(
2836
- () => resolve5({ exitCode, signal, timedOut, cancelled })
2836
+ () => resolve6({ exitCode, signal, timedOut, cancelled })
2837
2837
  );
2838
2838
  });
2839
2839
  });
@@ -2927,7 +2927,7 @@ import { basename, dirname as dirname2, isAbsolute, join as join2, resolve } fro
2927
2927
  import { spawn as spawn2 } from "node:child_process";
2928
2928
  var MAX_OUTPUT_BYTES = 1024 * 1024;
2929
2929
  function captureCommand(options) {
2930
- return new Promise((resolve5, reject) => {
2930
+ return new Promise((resolve6, reject) => {
2931
2931
  let stdout = "";
2932
2932
  let stderr = "";
2933
2933
  let timedOut = false;
@@ -2975,7 +2975,7 @@ function captureCommand(options) {
2975
2975
  settled = true;
2976
2976
  clearTimeout(timeout);
2977
2977
  options.signal.removeEventListener("abort", abort);
2978
- resolve5({
2978
+ resolve6({
2979
2979
  exitCode,
2980
2980
  signal,
2981
2981
  timedOut,
@@ -4432,7 +4432,200 @@ var applySkills = async (payload) => {
4432
4432
  // src/handlers/worktrees.ts
4433
4433
  import { watch } from "node:fs";
4434
4434
  import { readFile as readFile3, realpath as realpath4, stat as stat4 } from "node:fs/promises";
4435
- import { basename as basename2, dirname as dirname5, join as join5, relative as relative2 } from "node:path";
4435
+ import { basename as basename2, dirname as dirname5, join as join5, relative as relative3 } from "node:path";
4436
+
4437
+ // src/git-code-state.ts
4438
+ import { createHash as createHash2 } from "node:crypto";
4439
+ import { createReadStream as createReadStream2 } from "node:fs";
4440
+ import { lstat as lstat3, readlink } from "node:fs/promises";
4441
+ import { isAbsolute as isAbsolute3, relative as relative2, resolve as resolve4, sep as sep2 } from "node:path";
4442
+ import { spawn as spawn3 } from "node:child_process";
4443
+ function gitEnvironment() {
4444
+ return {
4445
+ ...process.env,
4446
+ GIT_TERMINAL_PROMPT: "0",
4447
+ GIT_OPTIONAL_LOCKS: "0"
4448
+ };
4449
+ }
4450
+ function remainingTimeoutMs(deadline) {
4451
+ return Math.max(1, deadline - Date.now());
4452
+ }
4453
+ function containedPath(folder, path) {
4454
+ const absolutePath = resolve4(folder, path);
4455
+ const difference = relative2(folder, absolutePath);
4456
+ if (!difference || difference === ".." || difference.startsWith(`..${sep2}`) || isAbsolute3(difference)) {
4457
+ return null;
4458
+ }
4459
+ return absolutePath;
4460
+ }
4461
+ function hashGitDiff(hash, folder, timeoutMs, signal) {
4462
+ return new Promise((resolveResult, reject) => {
4463
+ let settled = false;
4464
+ let timedOut = false;
4465
+ const child = spawn3(
4466
+ "git",
4467
+ [
4468
+ "-C",
4469
+ folder,
4470
+ "--no-optional-locks",
4471
+ "diff",
4472
+ "--binary",
4473
+ "--no-ext-diff",
4474
+ "--no-textconv",
4475
+ "HEAD",
4476
+ "--"
4477
+ ],
4478
+ {
4479
+ env: gitEnvironment(),
4480
+ shell: false,
4481
+ stdio: ["ignore", "pipe", "pipe"]
4482
+ }
4483
+ );
4484
+ child.stdout.on("data", (chunk) => hash.update(chunk));
4485
+ child.stderr.resume();
4486
+ const terminate = () => {
4487
+ if (child.exitCode === null && !child.killed) child.kill("SIGTERM");
4488
+ };
4489
+ const timeout = setTimeout(() => {
4490
+ timedOut = true;
4491
+ terminate();
4492
+ }, timeoutMs);
4493
+ timeout.unref();
4494
+ const abort = () => terminate();
4495
+ signal.addEventListener("abort", abort, { once: true });
4496
+ if (signal.aborted) abort();
4497
+ child.once("error", (error) => {
4498
+ if (settled) return;
4499
+ settled = true;
4500
+ clearTimeout(timeout);
4501
+ signal.removeEventListener("abort", abort);
4502
+ reject(error);
4503
+ });
4504
+ child.once("close", (exitCode) => {
4505
+ if (settled) return;
4506
+ settled = true;
4507
+ clearTimeout(timeout);
4508
+ signal.removeEventListener("abort", abort);
4509
+ resolveResult(!timedOut && !signal.aborted && exitCode === 0);
4510
+ });
4511
+ });
4512
+ }
4513
+ async function worktreeCodeStateHash(folder, timeoutMs, signal) {
4514
+ const deadline = Date.now() + timeoutMs;
4515
+ const operation = new AbortController();
4516
+ const abort = () => operation.abort(signal.reason);
4517
+ signal.addEventListener("abort", abort, { once: true });
4518
+ if (signal.aborted) abort();
4519
+ const timeout = setTimeout(() => operation.abort(), Math.max(0, timeoutMs));
4520
+ timeout.unref();
4521
+ try {
4522
+ operation.signal.throwIfAborted();
4523
+ const [head, untracked, submodules] = await Promise.all([
4524
+ captureCommand({
4525
+ command: "git",
4526
+ args: ["-C", folder, "rev-parse", "HEAD"],
4527
+ timeoutMs: remainingTimeoutMs(deadline),
4528
+ signal: operation.signal,
4529
+ env: gitEnvironment()
4530
+ }),
4531
+ captureCommand({
4532
+ command: "git",
4533
+ args: [
4534
+ "-C",
4535
+ folder,
4536
+ "--no-optional-locks",
4537
+ "ls-files",
4538
+ "--others",
4539
+ "--exclude-standard",
4540
+ "-z"
4541
+ ],
4542
+ timeoutMs: remainingTimeoutMs(deadline),
4543
+ signal: operation.signal,
4544
+ env: gitEnvironment()
4545
+ }),
4546
+ captureCommand({
4547
+ command: "git",
4548
+ args: [
4549
+ "-C",
4550
+ folder,
4551
+ "--no-optional-locks",
4552
+ "submodule",
4553
+ "foreach",
4554
+ "--quiet",
4555
+ 'printf "%s\\0" "$sm_path"'
4556
+ ],
4557
+ timeoutMs: remainingTimeoutMs(deadline),
4558
+ signal: operation.signal,
4559
+ env: gitEnvironment()
4560
+ })
4561
+ ]);
4562
+ if (head.exitCode !== 0 || untracked.exitCode !== 0 || submodules.exitCode !== 0 || operation.signal.aborted) {
4563
+ return null;
4564
+ }
4565
+ const hash = createHash2("sha256");
4566
+ hash.update("head\0");
4567
+ hash.update(head.stdout.trim());
4568
+ hash.update("\0diff\0");
4569
+ if (!await hashGitDiff(
4570
+ hash,
4571
+ folder,
4572
+ remainingTimeoutMs(deadline),
4573
+ operation.signal
4574
+ )) {
4575
+ return null;
4576
+ }
4577
+ hash.update("\0untracked\0");
4578
+ const paths = untracked.stdout.split("\0").filter(Boolean).sort();
4579
+ for (const path of paths) {
4580
+ operation.signal.throwIfAborted();
4581
+ const absolutePath = containedPath(folder, path);
4582
+ if (!absolutePath) return null;
4583
+ hash.update(path);
4584
+ hash.update("\0");
4585
+ const information = await lstat3(absolutePath);
4586
+ operation.signal.throwIfAborted();
4587
+ if (information.isSymbolicLink()) {
4588
+ hash.update("symlink\0");
4589
+ hash.update(await readlink(absolutePath));
4590
+ } else if (information.isFile()) {
4591
+ hash.update("file\0");
4592
+ hash.update((information.mode & 511).toString(8).padStart(3, "0"));
4593
+ hash.update("\0");
4594
+ for await (const chunk of createReadStream2(absolutePath, {
4595
+ signal: operation.signal
4596
+ })) {
4597
+ hash.update(chunk);
4598
+ }
4599
+ }
4600
+ hash.update("\0");
4601
+ }
4602
+ hash.update("submodules\0");
4603
+ const submodulePaths = submodules.stdout.split("\0").filter(Boolean).sort();
4604
+ for (const path of submodulePaths) {
4605
+ operation.signal.throwIfAborted();
4606
+ const absolutePath = containedPath(folder, path);
4607
+ if (!absolutePath) return null;
4608
+ const submoduleHash = await worktreeCodeStateHash(
4609
+ absolutePath,
4610
+ remainingTimeoutMs(deadline),
4611
+ operation.signal
4612
+ );
4613
+ if (!submoduleHash) return null;
4614
+ hash.update(path);
4615
+ hash.update("\0");
4616
+ hash.update(submoduleHash);
4617
+ hash.update("\0");
4618
+ }
4619
+ return hash.digest("hex");
4620
+ } catch {
4621
+ return null;
4622
+ } finally {
4623
+ clearTimeout(timeout);
4624
+ signal.removeEventListener("abort", abort);
4625
+ }
4626
+ }
4627
+
4628
+ // src/handlers/worktrees.ts
4436
4629
  var successfulProcess4 = {
4437
4630
  exitCode: 0,
4438
4631
  signal: null,
@@ -4482,27 +4675,30 @@ async function flushWorktreeActivity(entry) {
4482
4675
  entry.pending = false;
4483
4676
  try {
4484
4677
  const signal = new AbortController().signal;
4485
- const [status2, branchResult, headResult] = await Promise.all([
4486
- git2(
4487
- entry.folder,
4488
- [
4489
- "--no-optional-locks",
4490
- "status",
4491
- "--porcelain=v1",
4492
- "-z",
4493
- "--untracked-files=all"
4494
- ],
4495
- entry.timeoutMs,
4496
- signal
4497
- ),
4498
- git2(
4499
- entry.folder,
4500
- ["symbolic-ref", "--short", "-q", "HEAD"],
4501
- entry.timeoutMs,
4502
- signal
4503
- ),
4504
- git2(entry.folder, ["rev-parse", "HEAD"], entry.timeoutMs, signal)
4505
- ]);
4678
+ const [status2, branchResult, headResult, codeStateHash] = await Promise.all(
4679
+ [
4680
+ git2(
4681
+ entry.folder,
4682
+ [
4683
+ "--no-optional-locks",
4684
+ "status",
4685
+ "--porcelain=v1",
4686
+ "-z",
4687
+ "--untracked-files=all"
4688
+ ],
4689
+ entry.timeoutMs,
4690
+ signal
4691
+ ),
4692
+ git2(
4693
+ entry.folder,
4694
+ ["symbolic-ref", "--short", "-q", "HEAD"],
4695
+ entry.timeoutMs,
4696
+ signal
4697
+ ),
4698
+ git2(entry.folder, ["rev-parse", "HEAD"], entry.timeoutMs, signal),
4699
+ worktreeCodeStateHash(entry.folder, entry.timeoutMs, signal)
4700
+ ]
4701
+ );
4506
4702
  const branch = branchResult.exitCode === 0 ? branchResult.stdout.trim() : null;
4507
4703
  const headSha = headResult.exitCode === 0 ? headResult.stdout.trim() : null;
4508
4704
  const headIdentity = headSha ? `${branch ?? ""}\0${headSha}` : null;
@@ -4510,6 +4706,7 @@ async function flushWorktreeActivity(entry) {
4510
4706
  const report = {
4511
4707
  codebaseId: entry.codebaseId,
4512
4708
  gitDirectory: entry.gitDirectory,
4709
+ codeStateHash,
4513
4710
  ...changes,
4514
4711
  pushStatus: await worktreePushStatus(
4515
4712
  entry.folder,
@@ -4718,10 +4915,11 @@ async function inspectWorktreeItem(folderValue2, rootFolder, baseBranch, primary
4718
4915
  return {
4719
4916
  gitDirectory: gitDir,
4720
4917
  folder,
4721
- relativePath: relative2(rootFolder, folder) || ".",
4918
+ relativePath: relative3(rootFolder, folder) || ".",
4722
4919
  primary,
4723
4920
  branch,
4724
4921
  headSha: headResult.exitCode === 0 ? headResult.stdout.trim() : null,
4922
+ codeStateHash: await worktreeCodeStateHash(folder, timeoutMs, signal),
4725
4923
  upstream,
4726
4924
  ahead: upstreamCounts.ahead,
4727
4925
  behind: upstreamCounts.behind,
@@ -4744,10 +4942,11 @@ async function inspectWorktreeItem(folderValue2, rootFolder, baseBranch, primary
4744
4942
  return {
4745
4943
  gitDirectory: folderValue2,
4746
4944
  folder: folderValue2,
4747
- relativePath: relative2(rootFolder, folderValue2) || ".",
4945
+ relativePath: relative3(rootFolder, folderValue2) || ".",
4748
4946
  primary,
4749
4947
  branch: null,
4750
4948
  headSha: null,
4949
+ codeStateHash: null,
4751
4950
  upstream: null,
4752
4951
  ahead: null,
4753
4952
  behind: null,
@@ -5752,7 +5951,7 @@ var operateWorktree = async (payload, timeoutMs, signal) => {
5752
5951
  };
5753
5952
 
5754
5953
  // src/handlers/builds.ts
5755
- import { createHash as createHash2, randomUUID as randomUUID2 } from "node:crypto";
5954
+ import { createHash as createHash3, randomUUID as randomUUID2 } from "node:crypto";
5756
5955
  import {
5757
5956
  cp,
5758
5957
  mkdir as mkdir3,
@@ -5769,13 +5968,13 @@ import { tmpdir } from "node:os";
5769
5968
  import {
5770
5969
  basename as basename3,
5771
5970
  dirname as dirname6,
5772
- isAbsolute as isAbsolute3,
5971
+ isAbsolute as isAbsolute4,
5773
5972
  join as join6,
5774
- relative as relative3,
5775
- resolve as resolve4,
5776
- sep as sep2
5973
+ relative as relative4,
5974
+ resolve as resolve5,
5975
+ sep as sep3
5777
5976
  } from "node:path";
5778
- import { spawn as spawn3 } from "node:child_process";
5977
+ import { spawn as spawn4 } from "node:child_process";
5779
5978
  var successfulProcess5 = {
5780
5979
  exitCode: 0,
5781
5980
  signal: null,
@@ -5848,19 +6047,19 @@ async function validateWorktree2(input, timeoutMs, signal) {
5848
6047
  }
5849
6048
  return folder;
5850
6049
  }
5851
- function containedPath(root, relativePath) {
5852
- const target2 = resolve4(root, relativePath);
5853
- const difference = relative3(root, target2);
5854
- if (difference === ".." || difference.startsWith(`..${sep2}`) || isAbsolute3(difference)) {
6050
+ function containedPath2(root, relativePath) {
6051
+ const target2 = resolve5(root, relativePath);
6052
+ const difference = relative4(root, target2);
6053
+ if (difference === ".." || difference.startsWith(`..${sep3}`) || isAbsolute4(difference)) {
5855
6054
  throw new Error("Path must stay within the worktree");
5856
6055
  }
5857
6056
  return target2;
5858
6057
  }
5859
6058
  async function validateSource(root, source) {
5860
- const target2 = containedPath(root, source.relativePath);
6059
+ const target2 = containedPath2(root, source.relativePath);
5861
6060
  const resolved = await realpath5(target2);
5862
- const difference = relative3(root, resolved);
5863
- if (difference === ".." || difference.startsWith(`..${sep2}`) || isAbsolute3(difference)) {
6061
+ const difference = relative4(root, resolved);
6062
+ if (difference === ".." || difference.startsWith(`..${sep3}`) || isAbsolute4(difference)) {
5864
6063
  throw new Error("Build source resolves outside the worktree");
5865
6064
  }
5866
6065
  const information = await stat5(resolved);
@@ -5880,7 +6079,7 @@ async function discoverSourcesInFolder(folder) {
5880
6079
  for (const entry of await readdir4(current, { withFileTypes: true })) {
5881
6080
  if (entry.isSymbolicLink()) continue;
5882
6081
  const absolute = join6(current, entry.name);
5883
- const path = relative3(folder, absolute).split(sep2).join("/");
6082
+ const path = relative4(folder, absolute).split(sep3).join("/");
5884
6083
  if (entry.isDirectory()) {
5885
6084
  if (entry.name.endsWith(".xcodeproj")) {
5886
6085
  sources.push({ kind: "PROJECT", relativePath: path });
@@ -5971,15 +6170,15 @@ async function workspaceProjectPaths(workspacePath2, folder) {
5971
6170
  }).filter((path) => path.endsWith(".xcodeproj"));
5972
6171
  const projects = [];
5973
6172
  for (const location of locations) {
5974
- const candidate = resolve4(dirname6(workspacePath2), location);
5975
- const difference = relative3(folder, candidate);
5976
- if (difference === ".." || difference.startsWith(`..${sep2}`) || isAbsolute3(difference)) {
6173
+ const candidate = resolve5(dirname6(workspacePath2), location);
6174
+ const difference = relative4(folder, candidate);
6175
+ if (difference === ".." || difference.startsWith(`..${sep3}`) || isAbsolute4(difference)) {
5977
6176
  continue;
5978
6177
  }
5979
6178
  try {
5980
6179
  const resolved = await realpath5(candidate);
5981
- const resolvedDifference = relative3(folder, resolved);
5982
- if (resolvedDifference === ".." || resolvedDifference.startsWith(`..${sep2}`) || isAbsolute3(resolvedDifference) || !(await stat5(resolved)).isDirectory()) {
6180
+ const resolvedDifference = relative4(folder, resolved);
6181
+ if (resolvedDifference === ".." || resolvedDifference.startsWith(`..${sep3}`) || isAbsolute4(resolvedDifference) || !(await stat5(resolved)).isDirectory()) {
5983
6182
  continue;
5984
6183
  }
5985
6184
  projects.push(resolved);
@@ -6239,12 +6438,6 @@ var inspectBuildDestinations = async (payload, timeoutMs, signal) => {
6239
6438
  folder
6240
6439
  );
6241
6440
  requireSuccess3(preflight, "The saved scheme or configuration is unavailable");
6242
- if (GENERIC_BUILD_DESTINATION_ACTIONS.includes(input.action)) {
6243
- return {
6244
- ...successfulProcess5,
6245
- destinations: genericBuildDestinations(input.action)
6246
- };
6247
- }
6248
6441
  const [simulators, physical] = await Promise.all([
6249
6442
  command2(
6250
6443
  "xcrun",
@@ -6258,6 +6451,7 @@ var inspectBuildDestinations = async (payload, timeoutMs, signal) => {
6258
6451
  return {
6259
6452
  ...successfulProcess5,
6260
6453
  destinations: [
6454
+ ...genericBuildDestinations(input.action),
6261
6455
  ...simulators.exitCode === 0 ? simulatorDestinations(JSON.parse(simulators.stdout)) : [],
6262
6456
  ...physical
6263
6457
  ]
@@ -6406,7 +6600,7 @@ function runLoggedCommand(options) {
6406
6600
  mode: 384
6407
6601
  });
6408
6602
  }
6409
- const child = spawn3(options.command, options.args, {
6603
+ const child = spawn4(options.command, options.args, {
6410
6604
  cwd: options.cwd,
6411
6605
  env: options.env,
6412
6606
  shell: false,
@@ -6599,7 +6793,7 @@ function actionArgument(action) {
6599
6793
  }
6600
6794
  function xcodeBuildArguments(input) {
6601
6795
  const resultBundle = join6(input.artifactDirectory, "result.xcresult");
6602
- const sourcePath = containedPath(input.folder, input.source.relativePath);
6796
+ const sourcePath = containedPath2(input.folder, input.source.relativePath);
6603
6797
  const usesCapturedTestProducts = input.action === "TEST_WITHOUT_BUILDING";
6604
6798
  const args = [
6605
6799
  "xcodebuild",
@@ -6646,7 +6840,7 @@ function xcodeBuildSettingsArguments(input, folder = input.folder) {
6646
6840
  "xcodebuild",
6647
6841
  ...sourceArguments(
6648
6842
  input.source,
6649
- containedPath(folder, input.source.relativePath)
6843
+ containedPath2(folder, input.source.relativePath)
6650
6844
  ),
6651
6845
  "-scheme",
6652
6846
  input.scheme,
@@ -6749,7 +6943,7 @@ if (typeof hookModule.default === "function") {
6749
6943
  exitCode: result.exitCode,
6750
6944
  durationMs: Date.now() - started,
6751
6945
  causedBuildFailure: failed && options.script.failureBehavior === "FAIL_BUILD",
6752
- outputRelativePath: relative3(options.input.artifactDirectory, log),
6946
+ outputRelativePath: relative4(options.input.artifactDirectory, log),
6753
6947
  error: failed ? cleanError3(result.output || "Build hook failed") : null
6754
6948
  };
6755
6949
  } catch (hookError) {
@@ -6762,7 +6956,7 @@ if (typeof hookModule.default === "function") {
6762
6956
  exitCode: null,
6763
6957
  durationMs: Date.now() - started,
6764
6958
  causedBuildFailure: !options.signal.aborted && options.script.failureBehavior === "FAIL_BUILD",
6765
- outputRelativePath: relative3(options.input.artifactDirectory, log),
6959
+ outputRelativePath: relative4(options.input.artifactDirectory, log),
6766
6960
  error
6767
6961
  };
6768
6962
  }
@@ -6791,7 +6985,7 @@ async function fileChecksum(path) {
6791
6985
  const information = await stat5(path);
6792
6986
  if (!information.isFile() || information.size > 256 * 1024 * 1024)
6793
6987
  return null;
6794
- return createHash2("sha256").update(await readFile4(path)).digest("hex");
6988
+ return createHash3("sha256").update(await readFile4(path)).digest("hex");
6795
6989
  } catch {
6796
6990
  return null;
6797
6991
  }
@@ -6799,7 +6993,7 @@ async function fileChecksum(path) {
6799
6993
  async function artifact(root, kind, path, metadata = {}) {
6800
6994
  return {
6801
6995
  kind,
6802
- relativePath: relative3(root, path).split(sep2).join("/"),
6996
+ relativePath: relative4(root, path).split(sep3).join("/"),
6803
6997
  sizeBytes: await pathSize(path),
6804
6998
  checksum: await fileChecksum(path),
6805
6999
  metadata
@@ -6941,9 +7135,14 @@ var runIosBuild = async (payload, timeoutMs, signal, onLog, context) => {
6941
7135
  throw new Error("The captured .xctestrun artifact is unavailable");
6942
7136
  }
6943
7137
  }
6944
- if (!isAbsolute3(input.artifactDirectory) || basename3(input.artifactDirectory) !== input.buildId) {
7138
+ if (!isAbsolute4(input.artifactDirectory) || basename3(input.artifactDirectory) !== input.buildId) {
6945
7139
  throw new Error("Build artifact directory is invalid");
6946
7140
  }
7141
+ const sourceStateHash = await worktreeCodeStateHash(
7142
+ folder,
7143
+ Math.min(timeoutMs, 6e4),
7144
+ signal
7145
+ );
6947
7146
  await mkdir3(input.artifactDirectory, { recursive: true, mode: 448 });
6948
7147
  const rawLog = join6(input.artifactDirectory, "build.log");
6949
7148
  const logger = new BuildLogger(input.buildId, context, rawLog, process.env);
@@ -7099,6 +7298,12 @@ var runIosBuild = async (payload, timeoutMs, signal, onLog, context) => {
7099
7298
  const timedOut = buildResult?.timedOut === true;
7100
7299
  const exitCode = cancelled ? null : failBuild || !buildResult ? 1 : buildResult.exitCode;
7101
7300
  const args = xcodeBuildArguments(input);
7301
+ const finalStateHash = await worktreeCodeStateHash(
7302
+ folder,
7303
+ Math.min(timeoutMs, 6e4),
7304
+ new AbortController().signal
7305
+ );
7306
+ const codeStateObservedAt = (/* @__PURE__ */ new Date()).toISOString();
7102
7307
  return {
7103
7308
  exitCode,
7104
7309
  signal: buildResult?.signal ?? null,
@@ -7108,7 +7313,10 @@ var runIosBuild = async (payload, timeoutMs, signal, onLog, context) => {
7108
7313
  error,
7109
7314
  commandSummary: commandSummary("xcrun", args),
7110
7315
  artifacts,
7111
- scriptExecutions
7316
+ scriptExecutions,
7317
+ sourceStateHash,
7318
+ finalStateHash,
7319
+ codeStateObservedAt
7112
7320
  };
7113
7321
  } finally {
7114
7322
  await logger.close();
@@ -7133,10 +7341,10 @@ var downloadIosBuildArtifact = async (payload, timeoutMs, signal, onLog, context
7133
7341
  }
7134
7342
  const root = await realpath5(input.artifactDirectory);
7135
7343
  const target2 = await realpath5(
7136
- containedPath(root, input.artifactRelativePath)
7344
+ containedPath2(root, input.artifactRelativePath)
7137
7345
  );
7138
- const difference = relative3(root, target2);
7139
- if (!difference || difference === ".." || difference.startsWith(`..${sep2}`) || isAbsolute3(difference)) {
7346
+ const difference = relative4(root, target2);
7347
+ if (!difference || difference === ".." || difference.startsWith(`..${sep3}`) || isAbsolute4(difference)) {
7140
7348
  throw new Error("Build artifact resolves outside the build folder");
7141
7349
  }
7142
7350
  const information = await stat5(target2);
@@ -7209,7 +7417,7 @@ var deployIosBuild = async (payload, timeoutMs, signal, _onLog, context) => {
7209
7417
  Math.min(timeoutMs, 6e4),
7210
7418
  signal
7211
7419
  );
7212
- const appPath = containedPath(
7420
+ const appPath = containedPath2(
7213
7421
  input.artifactDirectory,
7214
7422
  input.artifactRelativePath
7215
7423
  );
@@ -7387,7 +7595,7 @@ var deployIosBuild = async (payload, timeoutMs, signal, _onLog, context) => {
7387
7595
  status: signal.aborted ? "CANCELLED" : failure ? "FAILED" : "SUCCEEDED",
7388
7596
  error: failure,
7389
7597
  durationMs: Date.now() - started,
7390
- outputRelativePath: relative3(input.artifactDirectory, logPath)
7598
+ outputRelativePath: relative4(input.artifactDirectory, logPath)
7391
7599
  });
7392
7600
  if (signal.aborted) break;
7393
7601
  }
@@ -7447,7 +7655,7 @@ var exportIosArchive = async (payload, timeoutMs, signal, _onLog, context) => {
7447
7655
  Math.min(timeoutMs, 6e4),
7448
7656
  signal
7449
7657
  );
7450
- const archivePath = containedPath(
7658
+ const archivePath = containedPath2(
7451
7659
  input.artifactDirectory,
7452
7660
  input.archiveRelativePath
7453
7661
  );
@@ -7493,7 +7701,7 @@ var exportIosArchive = async (payload, timeoutMs, signal, _onLog, context) => {
7493
7701
  signal: result.signal,
7494
7702
  timedOut: result.timedOut,
7495
7703
  cancelled: result.cancelled,
7496
- outputRelativePath: relative3(input.artifactDirectory, exportDirectory),
7704
+ outputRelativePath: relative4(input.artifactDirectory, exportDirectory),
7497
7705
  sizeBytes: result.exitCode === 0 ? await pathSize(exportDirectory) : null,
7498
7706
  error: result.exitCode === 0 ? null : cleanError3(result.output)
7499
7707
  };
@@ -7542,8 +7750,8 @@ var RepositoryCoordinator = class {
7542
7750
  async run(key, task) {
7543
7751
  const previous = this.tails.get(key) ?? Promise.resolve();
7544
7752
  let release2;
7545
- const current = new Promise((resolve5) => {
7546
- release2 = resolve5;
7753
+ const current = new Promise((resolve6) => {
7754
+ release2 = resolve6;
7547
7755
  });
7548
7756
  const tail = previous.then(() => current);
7549
7757
  this.tails.set(key, tail);
@@ -7641,7 +7849,7 @@ var JobExecutor = class {
7641
7849
  );
7642
7850
  if (this.stopping) return;
7643
7851
  const delay = Math.min(3e4, 1e3 * 2 ** Math.min(retry++, 5));
7644
- await new Promise((resolve5) => setTimeout(resolve5, delay));
7852
+ await new Promise((resolve6) => setTimeout(resolve6, delay));
7645
7853
  }
7646
7854
  }
7647
7855
  }
@@ -7912,9 +8120,9 @@ async function runAgent(config, signal) {
7912
8120
  () => void heartbeat(),
7913
8121
  HEARTBEAT_INTERVAL_MS
7914
8122
  );
7915
- await new Promise((resolve5) => {
7916
- if (signal.aborted) resolve5();
7917
- else signal.addEventListener("abort", () => resolve5(), { once: true });
8123
+ await new Promise((resolve6) => {
8124
+ if (signal.aborted) resolve6();
8125
+ else signal.addEventListener("abort", () => resolve6(), { once: true });
7918
8126
  });
7919
8127
  clearInterval(heartbeatTimer);
7920
8128
  if (codebaseTimer) clearTimeout(codebaseTimer);
@@ -7931,7 +8139,7 @@ var defaultDependencies = {
7931
8139
  inventory: collectInventory,
7932
8140
  load: loadConfig,
7933
8141
  save: saveConfig,
7934
- wait: (milliseconds, signal) => new Promise((resolve5, reject) => {
8142
+ wait: (milliseconds, signal) => new Promise((resolve6, reject) => {
7935
8143
  if (signal.aborted) {
7936
8144
  reject(new Error("Development agent startup was cancelled"));
7937
8145
  return;
@@ -7942,7 +8150,7 @@ var defaultDependencies = {
7942
8150
  };
7943
8151
  const timer = setTimeout(() => {
7944
8152
  signal.removeEventListener("abort", onAbort);
7945
- resolve5();
8153
+ resolve6();
7946
8154
  }, milliseconds);
7947
8155
  signal.addEventListener("abort", onAbort, { once: true });
7948
8156
  })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-development-environment/control-agent",
3
- "version": "0.0.27",
3
+ "version": "0.0.29",
4
4
  "description": "Control agent for the AI Development Environment",
5
5
  "license": "MIT",
6
6
  "repository": {