@ai-development-environment/control-agent 0.0.134 → 0.0.136

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 +343 -23
  2. package/package.json +1 -1
@@ -6752,6 +6752,14 @@ var AgentGraphQLClient = class {
6752
6752
  { jobId, attemptId, chunks }
6753
6753
  );
6754
6754
  }
6755
+ reportBuildDeploymentLaunched(jobId, deploymentId, launchedAt) {
6756
+ return this.request(
6757
+ `mutation ReportBuildDeploymentLaunched($jobId: ID!, $deploymentId: ID!, $launchedAt: String!) {
6758
+ reportBuildDeploymentLaunched(jobId: $jobId, deploymentId: $deploymentId, launchedAt: $launchedAt)
6759
+ }`,
6760
+ { jobId, deploymentId, launchedAt }
6761
+ );
6762
+ }
6755
6763
  reportBuildProgress(input) {
6756
6764
  return this.request(
6757
6765
  `mutation ReportBuildProgress($input: BuildProgressInput!) {
@@ -8133,6 +8141,7 @@ var WORKTREE_INSPECT_JOB_KIND = "worktree.inspect";
8133
8141
  var WORKTREE_OPERATION_JOB_KIND = "worktree.operation";
8134
8142
  var WORKTREE_COMMIT_JOB_KIND = "worktree.commit";
8135
8143
  var WORKTREE_AUTO_SYNC_JOB_KIND = "worktree.auto-sync";
8144
+ var WORKTREE_REVIEWED_SYNC_CAPABILITY = "worktree.auto-sync.reviewed-head";
8136
8145
  var WORKTREE_PREPARATION_JOB_KIND = "worktree.prepare";
8137
8146
  var WORKTREE_GIT_INSPECT_JOB_KIND = "worktree.git.inspect";
8138
8147
  var WORKTREE_GIT_OPERATION_JOB_KIND = "worktree.git.operation";
@@ -8142,6 +8151,7 @@ var JIRA_BRANCH_GENERATE_JOB_KIND = "jira.branch.generate";
8142
8151
  var WORKTREE_MOVE_PUSH_JOB_KIND = "worktree.move.push";
8143
8152
  var WORKTREE_MOVE_CHECKOUT_JOB_KIND = "worktree.move.checkout";
8144
8153
  var WORKTREE_DELETE_JOB_KIND = "worktree.delete";
8154
+ var WORKTREE_DELETE_BUILD_DATA_CAPABILITY = "worktree.delete.xcode-cleanup";
8145
8155
  var WORKTREE_DIFF_JOB_KIND = "worktree.diff.inspect";
8146
8156
  var WORKTREE_DIFF_ASSET_JOB_KIND = "worktree.diff.asset";
8147
8157
  var WORKTREE_JOB_KINDS = [
@@ -8560,6 +8570,7 @@ function worktreeMoveCheckoutJobPayload(value) {
8560
8570
  function worktreeDeleteJobPayload(value) {
8561
8571
  const payload = objectValue4(value, "worktree delete payload");
8562
8572
  const allowed = /* @__PURE__ */ new Set([
8573
+ "derivedData",
8563
8574
  "moveId",
8564
8575
  "codebaseId",
8565
8576
  "rootFolder",
@@ -8593,7 +8604,20 @@ function worktreeDeleteJobPayload(value) {
8593
8604
  if (payload.requireClean && !expectedHeadSha) {
8594
8605
  throw new Error("Clean worktree deletion requires an expected HEAD");
8595
8606
  }
8607
+ const derivedData = payload.derivedData === void 0 ? { mode: "DEFAULT", path: null } : objectValue4(payload.derivedData, "worktree delete payload.derivedData");
8608
+ const unexpectedDerivedData = Object.keys(derivedData).find(
8609
+ (key) => !["mode", "path"].includes(key)
8610
+ );
8611
+ if (unexpectedDerivedData)
8612
+ throw new Error(`Unexpected derived data field: ${unexpectedDerivedData}`);
8596
8613
  return {
8614
+ derivedData: {
8615
+ mode: parseDerivedDataLocationMode(derivedData.mode),
8616
+ path: nullableString3(
8617
+ derivedData.path,
8618
+ "worktree delete payload.derivedData.path"
8619
+ )
8620
+ },
8597
8621
  moveId: nullableString3(payload.moveId, "worktree delete payload.moveId"),
8598
8622
  codebaseId: stringValue4(
8599
8623
  payload.codebaseId,
@@ -8749,7 +8773,8 @@ function worktreeAutoSyncJobPayload(value) {
8749
8773
  "baseBranch",
8750
8774
  "phase",
8751
8775
  "forcePreparations",
8752
- "preparations"
8776
+ "preparations",
8777
+ "mergeGuard"
8753
8778
  ]);
8754
8779
  const unexpected = Object.keys(payload).find((key) => !allowed.has(key));
8755
8780
  if (unexpected) {
@@ -8765,6 +8790,27 @@ function worktreeAutoSyncJobPayload(value) {
8765
8790
  "worktree auto sync payload.forcePreparations must be a boolean"
8766
8791
  );
8767
8792
  }
8793
+ let mergeGuard;
8794
+ if (payload.mergeGuard !== void 0) {
8795
+ const guard = objectValue4(payload.mergeGuard, "merge sync guard");
8796
+ mergeGuard = {
8797
+ expectedHeadSha: stringValue4(
8798
+ guard.expectedHeadSha,
8799
+ "merge sync expected head"
8800
+ ),
8801
+ targetUrl: stringValue4(guard.targetUrl, "merge sync target URL"),
8802
+ targetBranch: stringValue4(guard.targetBranch, "merge sync target branch"),
8803
+ targetSha: stringValue4(guard.targetSha, "merge sync target SHA")
8804
+ };
8805
+ if (!/^[a-f0-9]{40,64}$/i.test(mergeGuard.expectedHeadSha))
8806
+ throw new Error("Invalid merge sync source SHA");
8807
+ if (!/^[a-f0-9]{40,64}$/i.test(mergeGuard.targetSha))
8808
+ throw new Error("Invalid merge sync target SHA");
8809
+ if (!validGitBranchName(mergeGuard.targetBranch))
8810
+ throw new Error("Invalid merge sync target branch");
8811
+ if (payload.phase !== "SYNC")
8812
+ throw new Error("Reviewed sync requires the SYNC phase");
8813
+ }
8768
8814
  return {
8769
8815
  codebaseId: stringValue4(
8770
8816
  payload.codebaseId,
@@ -8788,6 +8834,7 @@ function worktreeAutoSyncJobPayload(value) {
8788
8834
  "worktree auto sync payload.baseBranch"
8789
8835
  ),
8790
8836
  phase: payload.phase,
8837
+ ...mergeGuard ? { mergeGuard } : {},
8791
8838
  forcePreparations: payload.forcePreparations === true,
8792
8839
  preparations: preparationDefinitions(
8793
8840
  payload.preparations ?? [],
@@ -11152,7 +11199,7 @@ function tailscaleServeRemovePayload(value) {
11152
11199
  // package.json
11153
11200
  var package_default = {
11154
11201
  name: "@ai-development-environment/control-agent",
11155
- version: "0.0.134",
11202
+ version: "0.0.136",
11156
11203
  description: "Control agent for the AI Development Environment",
11157
11204
  license: "MIT",
11158
11205
  repository: {
@@ -11223,6 +11270,7 @@ var PORTABLE_AGENT_CAPABILITIES = [
11223
11270
  ...CODEBASE_JOB_KINDS,
11224
11271
  CODEBASE_RECONCILE_EVENT_CAPABILITY,
11225
11272
  ...WORKTREE_JOB_KINDS,
11273
+ WORKTREE_REVIEWED_SYNC_CAPABILITY,
11226
11274
  ...SKILL_JOB_KINDS,
11227
11275
  ...COVERAGE_JOB_KINDS,
11228
11276
  "runs.protocol.v1",
@@ -11233,6 +11281,7 @@ var PORTABLE_AGENT_CAPABILITIES = [
11233
11281
  ...WORKFLOW_JOB_KINDS
11234
11282
  ];
11235
11283
  var MACOS_AGENT_CAPABILITIES = [
11284
+ WORKTREE_DELETE_BUILD_DATA_CAPABILITY,
11236
11285
  ...BUILD_DATA_JOB_KINDS,
11237
11286
  ...IOS_BUILD_JOB_KINDS,
11238
11287
  ...SIGNING_ASSET_JOB_KINDS,
@@ -47672,6 +47721,7 @@ async function deployPreparedIosBuild(input, folder, appPath, timeoutMs, signal2
47672
47721
  const logPath = join12(directory, "deployment.log");
47673
47722
  const started = Date.now();
47674
47723
  let failure = null;
47724
+ let launchedAt = null;
47675
47725
  try {
47676
47726
  if (deployment.destination.type === "SIMULATOR") {
47677
47727
  if (deployment.destination.state !== "Booted") {
@@ -47719,7 +47769,9 @@ async function deployPreparedIosBuild(input, folder, appPath, timeoutMs, signal2
47719
47769
  logPath
47720
47770
  });
47721
47771
  if (bootStatus.exitCode !== 0)
47722
- failure = cleanError2(bootStatus.output);
47772
+ failure = cleanError2(
47773
+ bootStatus.output || "Simulator did not finish booting"
47774
+ );
47723
47775
  }
47724
47776
  if (!failure) {
47725
47777
  const install = await runSimpleLogged(logger, {
@@ -47731,7 +47783,10 @@ async function deployPreparedIosBuild(input, folder, appPath, timeoutMs, signal2
47731
47783
  scopeId: deployment.id,
47732
47784
  logPath
47733
47785
  });
47734
- if (install.exitCode !== 0) failure = cleanError2(install.output);
47786
+ if (install.exitCode !== 0)
47787
+ failure = cleanError2(
47788
+ install.output || "Could not install the app"
47789
+ );
47735
47790
  }
47736
47791
  if (!failure) {
47737
47792
  const launch = await runSimpleLogged(logger, {
@@ -47748,7 +47803,9 @@ async function deployPreparedIosBuild(input, folder, appPath, timeoutMs, signal2
47748
47803
  scopeId: deployment.id,
47749
47804
  logPath
47750
47805
  });
47751
- if (launch.exitCode !== 0) failure = cleanError2(launch.output);
47806
+ if (launch.exitCode !== 0)
47807
+ failure = cleanError2(launch.output || "Could not launch the app");
47808
+ else launchedAt = (/* @__PURE__ */ new Date()).toISOString();
47752
47809
  }
47753
47810
  } else {
47754
47811
  const verify = await runLoggedCommand({
@@ -47784,7 +47841,10 @@ async function deployPreparedIosBuild(input, folder, appPath, timeoutMs, signal2
47784
47841
  scopeId: deployment.id,
47785
47842
  logPath
47786
47843
  });
47787
- if (install.exitCode !== 0) failure = cleanError2(install.output);
47844
+ if (install.exitCode !== 0)
47845
+ failure = cleanError2(
47846
+ install.output || "Could not install the app"
47847
+ );
47788
47848
  }
47789
47849
  if (!failure) {
47790
47850
  const launch = await runSimpleLogged(logger, {
@@ -47805,7 +47865,9 @@ async function deployPreparedIosBuild(input, folder, appPath, timeoutMs, signal2
47805
47865
  scopeId: deployment.id,
47806
47866
  logPath
47807
47867
  });
47808
- if (launch.exitCode !== 0) failure = cleanError2(launch.output);
47868
+ if (launch.exitCode !== 0)
47869
+ failure = cleanError2(launch.output || "Could not launch the app");
47870
+ else launchedAt = (/* @__PURE__ */ new Date()).toISOString();
47809
47871
  }
47810
47872
  }
47811
47873
  } catch (deploymentError) {
@@ -47818,10 +47880,27 @@ async function deployPreparedIosBuild(input, folder, appPath, timeoutMs, signal2
47818
47880
  deployment.id
47819
47881
  );
47820
47882
  }
47883
+ if (launchedAt && context?.reportBuildDeploymentLaunched) {
47884
+ try {
47885
+ await context.reportBuildDeploymentLaunched(
47886
+ deployment.id,
47887
+ launchedAt
47888
+ );
47889
+ } catch (error) {
47890
+ logger.emit(
47891
+ "LAUNCH",
47892
+ "SYSTEM",
47893
+ `Launch reporting will be retried on completion: ${cleanError2(error)}`,
47894
+ "DEPLOYMENT",
47895
+ deployment.id
47896
+ );
47897
+ }
47898
+ }
47821
47899
  outcomes.push({
47822
47900
  id: deployment.id,
47823
47901
  status: signal2.aborted ? "CANCELLED" : failure ? "FAILED" : "SUCCEEDED",
47824
47902
  error: failure,
47903
+ launchedAt,
47825
47904
  durationMs: Date.now() - started,
47826
47905
  outputRelativePath: relative4(input.artifactDirectory, logPath)
47827
47906
  });
@@ -49226,7 +49305,14 @@ import {
49226
49305
  statfs
49227
49306
  } from "node:fs/promises";
49228
49307
  import { homedir as homedir12 } from "node:os";
49229
- import { basename as basename4, dirname as dirname8, isAbsolute as isAbsolute6, join as join16, resolve as resolve7 } from "node:path";
49308
+ import {
49309
+ basename as basename4,
49310
+ dirname as dirname8,
49311
+ isAbsolute as isAbsolute6,
49312
+ join as join16,
49313
+ relative as relative7,
49314
+ resolve as resolve7
49315
+ } from "node:path";
49230
49316
  var successfulProcess7 = {
49231
49317
  exitCode: 0,
49232
49318
  signal: null,
@@ -49388,6 +49474,53 @@ var scanBuildData = async (rawPayload, timeoutMs, signal2) => {
49388
49474
  if (deviceSupport.warning) warnings.push(deviceSupport.warning);
49389
49475
  return { ...successfulProcess7, entries, warnings };
49390
49476
  };
49477
+ async function ownedWorktreeBuildData(folder, configuration, timeoutMs, signal2) {
49478
+ const within = (parent, child) => {
49479
+ const path = relative7(parent, child);
49480
+ return path === "" || !path.startsWith(`..${process.platform === "win32" ? "\\" : "/"}`) && path !== ".." && !isAbsolute6(path);
49481
+ };
49482
+ const roots = await configuredRoots({
49483
+ ...configuration,
49484
+ worktrees: [{ id: "deleted-worktree", folder }]
49485
+ });
49486
+ const targets = [];
49487
+ const warnings = [];
49488
+ for (const root of new Set(roots)) {
49489
+ const report = await scanRoot(root, timeoutMs, signal2);
49490
+ if (report.warning) warnings.push(report.warning);
49491
+ for (const entry of report.entries) {
49492
+ if (entry.kind !== "PROJECT" || !entry.workspacePath || !isAbsolute6(entry.workspacePath) || entry.name.toLowerCase().endsWith(".noindex"))
49493
+ continue;
49494
+ if (!within(folder, resolve7(entry.workspacePath)) || within(folder, entry.path))
49495
+ continue;
49496
+ try {
49497
+ const workspace = await realpath6(entry.workspacePath);
49498
+ if (!within(folder, workspace)) continue;
49499
+ } catch (error) {
49500
+ if (error.code !== "ENOENT") {
49501
+ warnings.push(`${entry.path}: ${errorMessage(error)}`);
49502
+ continue;
49503
+ }
49504
+ let ancestor = dirname8(entry.workspacePath);
49505
+ while (within(folder, ancestor) && ancestor !== folder) {
49506
+ try {
49507
+ if (!within(folder, await realpath6(ancestor))) ancestor = "";
49508
+ break;
49509
+ } catch (parentError) {
49510
+ if (parentError.code !== "ENOENT") {
49511
+ ancestor = "";
49512
+ break;
49513
+ }
49514
+ ancestor = dirname8(ancestor);
49515
+ }
49516
+ }
49517
+ if (!ancestor) continue;
49518
+ }
49519
+ targets.push({ path: entry.path, rootPath: entry.rootPath });
49520
+ }
49521
+ }
49522
+ return { targets, warnings };
49523
+ }
49391
49524
  function assertDirectChild(rootPath2, path) {
49392
49525
  if (!isAbsolute6(rootPath2) || !isAbsolute6(path)) {
49393
49526
  throw new Error("Build Data targets must use absolute paths");
@@ -49636,7 +49769,7 @@ import {
49636
49769
  writeFile as writeFile8
49637
49770
  } from "node:fs/promises";
49638
49771
  import { homedir as homedir13 } from "node:os";
49639
- import { dirname as dirname9, isAbsolute as isAbsolute7, join as join17, relative as relative7, resolve as resolve8, sep as sep8 } from "node:path";
49772
+ import { dirname as dirname9, isAbsolute as isAbsolute7, join as join17, relative as relative8, resolve as resolve8, sep as sep8 } from "node:path";
49640
49773
  import { promisify as promisify3 } from "node:util";
49641
49774
  var executeFile = promisify3(execFile3);
49642
49775
  var successfulProcess8 = {
@@ -49742,7 +49875,7 @@ async function listPackageFiles(skillDirectory) {
49742
49875
  const metadata = await lstat4(path);
49743
49876
  if (metadata.isSymbolicLink()) {
49744
49877
  throw new Error(
49745
- `Nested symbolic link is not supported: ${relative7(skillDirectory, path)}`
49878
+ `Nested symbolic link is not supported: ${relative8(skillDirectory, path)}`
49746
49879
  );
49747
49880
  }
49748
49881
  if (metadata.isDirectory()) {
@@ -49754,14 +49887,14 @@ async function listPackageFiles(skillDirectory) {
49754
49887
  throw new Error("Skill contains too many files");
49755
49888
  if (metadata.size > MAX_SKILL_FILE_BYTES) {
49756
49889
  throw new Error(
49757
- `${relative7(skillDirectory, path)} exceeds the per-file limit`
49890
+ `${relative8(skillDirectory, path)} exceeds the per-file limit`
49758
49891
  );
49759
49892
  }
49760
49893
  totalBytes += metadata.size;
49761
49894
  if (totalBytes > MAX_SKILL_PACKAGE_BYTES)
49762
49895
  throw new Error("Skill exceeds the package size limit");
49763
49896
  files.push({
49764
- path: relative7(skillDirectory, path).split(sep8).join("/"),
49897
+ path: relative8(skillDirectory, path).split(sep8).join("/"),
49765
49898
  contentsBase64: (await readFile8(path)).toString("base64"),
49766
49899
  executable: (metadata.mode & 73) !== 0
49767
49900
  });
@@ -49840,7 +49973,7 @@ async function scanRoot2(location, configured, targets, warnings) {
49840
49973
  const directory = join17(root, entry.name);
49841
49974
  try {
49842
49975
  const skillPackage = await readPackage(directory, resolvedRoot);
49843
- const relativeDirectory = location.folder ? relative7(location.folder, directory).split(sep8).join("/") : "";
49976
+ const relativeDirectory = location.folder ? relative8(location.folder, directory).split(sep8).join("/") : "";
49844
49977
  installations.push({
49845
49978
  ...location,
49846
49979
  codebaseId: target2?.codebaseId ?? null,
@@ -49967,7 +50100,7 @@ ${MANAGED_EXCLUDE_END}` : "";
49967
50100
  }
49968
50101
  async function assertUntracked(location, skillDirectory) {
49969
50102
  if (location.scope !== "PROJECT") return;
49970
- const relativeDirectory = relative7(location.folder, skillDirectory).split(sep8).join("/");
50103
+ const relativeDirectory = relative8(location.folder, skillDirectory).split(sep8).join("/");
49971
50104
  if (await trackedProjectPath(location.folder, relativeDirectory)) {
49972
50105
  throw new Error(
49973
50106
  `${relativeDirectory} is tracked by Git and cannot be managed automatically`
@@ -50041,7 +50174,7 @@ var applySkills = async (payload) => {
50041
50174
  if (operation.scope === "PROJECT" && operation.manageGitExclude) {
50042
50175
  await updateManagedExclude(
50043
50176
  operation.folder,
50044
- relative7(operation.folder, destination),
50177
+ relative8(operation.folder, destination),
50045
50178
  operation.kind === "WRITE"
50046
50179
  );
50047
50180
  }
@@ -50452,7 +50585,7 @@ var runWorkflowGitCheckpoint = async (payloadValue, _timeoutMs, signal2) => {
50452
50585
  import { createWriteStream as createWriteStream3, watch } from "node:fs";
50453
50586
  import { mkdtemp as mkdtemp9, open as open3, readFile as readFile11, realpath as realpath10, rm as rm18, stat as stat11 } from "node:fs/promises";
50454
50587
  import { tmpdir as tmpdir9 } from "node:os";
50455
- import { basename as basename5, dirname as dirname11, isAbsolute as isAbsolute9, join as join21, relative as relative9 } from "node:path";
50588
+ import { basename as basename5, dirname as dirname11, isAbsolute as isAbsolute9, join as join21, relative as relative10 } from "node:path";
50456
50589
  import { spawn as spawn9 } from "node:child_process";
50457
50590
 
50458
50591
  // src/handlers/worktree-preparations.ts
@@ -50466,7 +50599,7 @@ import {
50466
50599
  rm as rm17,
50467
50600
  writeFile as writeFile10
50468
50601
  } from "node:fs/promises";
50469
- import { dirname as dirname10, isAbsolute as isAbsolute8, join as join20, relative as relative8, resolve as resolve9 } from "node:path";
50602
+ import { dirname as dirname10, isAbsolute as isAbsolute8, join as join20, relative as relative9, resolve as resolve9 } from "node:path";
50470
50603
  var EXCLUDE_BEGIN = "# BEGIN AIDE PREPARATIONS";
50471
50604
  var EXCLUDE_END = "# END AIDE PREPARATIONS";
50472
50605
  function cleanError3(value) {
@@ -50508,7 +50641,7 @@ async function exists(path) {
50508
50641
  async function safeTarget(folder, path) {
50509
50642
  const root = await realpath9(folder);
50510
50643
  const target2 = resolve9(root, path);
50511
- const fromRoot = relative8(root, target2);
50644
+ const fromRoot = relative9(root, target2);
50512
50645
  if (!fromRoot || fromRoot.startsWith("..") || isAbsolute8(fromRoot) || path.includes("\0")) {
50513
50646
  throw new Error(
50514
50647
  "Preparation path must be an exact file inside the worktree"
@@ -51300,7 +51433,7 @@ async function inspectWorktreeItem(folderValue2, rootFolder, baseBranch, primary
51300
51433
  return {
51301
51434
  gitDirectory: gitDir,
51302
51435
  folder,
51303
- relativePath: relative9(rootFolder, folder) || ".",
51436
+ relativePath: relative10(rootFolder, folder) || ".",
51304
51437
  primary,
51305
51438
  branch,
51306
51439
  headSha: headResult.exitCode === 0 ? headResult.stdout.trim() : null,
@@ -51328,7 +51461,7 @@ async function inspectWorktreeItem(folderValue2, rootFolder, baseBranch, primary
51328
51461
  return {
51329
51462
  gitDirectory: folderValue2,
51330
51463
  folder: folderValue2,
51331
- relativePath: relative9(rootFolder, folderValue2) || ".",
51464
+ relativePath: relative10(rootFolder, folderValue2) || ".",
51332
51465
  primary,
51333
51466
  branch: null,
51334
51467
  headSha: null,
@@ -51782,7 +51915,7 @@ async function availableSide(side, folder, timeoutMs, signal2) {
51782
51915
  }
51783
51916
  try {
51784
51917
  const resolved = await realpath10(side.path);
51785
- const difference = relative9(folder, resolved);
51918
+ const difference = relative10(folder, resolved);
51786
51919
  return difference !== ".." && !difference.startsWith("../") && (await stat11(resolved)).isFile();
51787
51920
  } catch {
51788
51921
  return false;
@@ -52019,7 +52152,7 @@ var downloadWorktreeDiffAsset = async (payload, timeoutMs, signal2, _onLog, cont
52019
52152
  try {
52020
52153
  if (selected.kind === "FILE") {
52021
52154
  uploadPath = await realpath10(selected.path);
52022
- const difference = relative9(folder, uploadPath);
52155
+ const difference = relative10(folder, uploadPath);
52023
52156
  if (difference === ".." || difference.startsWith("../")) {
52024
52157
  throw new Error("Diff image resolves outside the worktree");
52025
52158
  }
@@ -53013,6 +53146,26 @@ var deleteWorktree = async (payload, timeoutMs, signal2) => {
53013
53146
  requireSuccess4(remote, `Could not inspect origin/${branch}`);
53014
53147
  }
53015
53148
  }
53149
+ const buildDataCleanup = {
53150
+ deleted: [],
53151
+ warnings: []
53152
+ };
53153
+ let buildDataTargets = [];
53154
+ try {
53155
+ const owned = await ownedWorktreeBuildData(
53156
+ folder,
53157
+ input.derivedData,
53158
+ timeoutMs,
53159
+ signal2
53160
+ );
53161
+ buildDataTargets = owned.targets;
53162
+ buildDataCleanup.warnings.push(...owned.warnings);
53163
+ } catch (error) {
53164
+ signal2.throwIfAborted();
53165
+ buildDataCleanup.warnings.push(
53166
+ `Could not inspect Xcode build data: ${error instanceof Error ? error.message : String(error)}`
53167
+ );
53168
+ }
53016
53169
  requireSuccess4(
53017
53170
  await git4(
53018
53171
  rootFolder,
@@ -53022,6 +53175,26 @@ var deleteWorktree = async (payload, timeoutMs, signal2) => {
53022
53175
  ),
53023
53176
  "Could not remove the worktree"
53024
53177
  );
53178
+ if (buildDataTargets.length) {
53179
+ try {
53180
+ const result = await deleteBuildData(
53181
+ { targets: buildDataTargets },
53182
+ timeoutMs,
53183
+ signal2,
53184
+ async () => void 0
53185
+ );
53186
+ buildDataCleanup.deleted.push(...result.deleted);
53187
+ buildDataCleanup.warnings.push(
53188
+ ...result.deleted.filter((entry) => !entry.deleted).map(
53189
+ (entry) => `${entry.path}: ${entry.error ?? "Could not remove Xcode build data"}`
53190
+ )
53191
+ );
53192
+ } catch (error) {
53193
+ buildDataCleanup.warnings.push(
53194
+ `Could not remove Xcode build data: ${error instanceof Error ? error.message : String(error)}`
53195
+ );
53196
+ }
53197
+ }
53025
53198
  if (branch && await refExists(rootFolder, `refs/heads/${branch}`, timeoutMs, signal2)) {
53026
53199
  requireSuccess4(
53027
53200
  await git4(rootFolder, ["branch", "-D", branch], timeoutMs, signal2),
@@ -53033,7 +53206,8 @@ var deleteWorktree = async (payload, timeoutMs, signal2) => {
53033
53206
  moveId: input.moveId,
53034
53207
  deleted: true,
53035
53208
  branch,
53036
- remoteBranchDeleted: input.deleteRemoteBranch
53209
+ remoteBranchDeleted: input.deleteRemoteBranch,
53210
+ buildDataCleanup
53037
53211
  };
53038
53212
  };
53039
53213
  var operateWorktree = async (payload, timeoutMs, signal2) => {
@@ -53362,6 +53536,145 @@ var operateWorktree = async (payload, timeoutMs, signal2) => {
53362
53536
  worktree: await worktreeResult()
53363
53537
  };
53364
53538
  };
53539
+ async function syncReviewedBranch(input, folder, timeoutMs, signal2) {
53540
+ const guard = input.mergeGuard;
53541
+ const run = async (args, message2) => {
53542
+ const result2 = await git4(folder, args, timeoutMs, signal2);
53543
+ if (result2.exitCode !== 0)
53544
+ throw new Error(
53545
+ `${message2}: ${cleanError4(result2.stderr || result2.stdout)}`
53546
+ );
53547
+ return result2;
53548
+ };
53549
+ const head = async () => (await run(["rev-parse", "HEAD"], "Could not read HEAD")).stdout.trim();
53550
+ const clean2 = async () => {
53551
+ if ((await run(
53552
+ ["status", "--porcelain=v1", "-z"],
53553
+ "Could not inspect changes"
53554
+ )).stdout)
53555
+ throw new Error("Stash or commit changes before Auto Merge can sync");
53556
+ };
53557
+ if (await head() !== guard.expectedHeadSha)
53558
+ throw new Error(
53559
+ "The worktree source commit changed. Review it before resuming Auto Merge."
53560
+ );
53561
+ await clean2();
53562
+ const remoteHead = (await run(
53563
+ [
53564
+ "ls-remote",
53565
+ "--heads",
53566
+ "--",
53567
+ "origin",
53568
+ `refs/heads/${input.expectedBranch}`
53569
+ ],
53570
+ "Could not read the remote source revision"
53571
+ )).stdout.trim().split(/\s+/)[0];
53572
+ if (remoteHead !== guard.expectedHeadSha)
53573
+ throw new Error(
53574
+ "The remote source commit changed. Review it before resuming Auto Merge."
53575
+ );
53576
+ const targetOrigin = normalizeGitOrigin(guard.targetUrl).canonicalOrigin;
53577
+ let targetTransport = targetOrigin === input.expectedOrigin ? "origin" : guard.targetUrl;
53578
+ if (targetTransport !== "origin" && /^https?:/.test(guard.targetUrl)) {
53579
+ const sourceUrl = (await run(["remote", "get-url", "origin"], "Could not read origin")).stdout.trim();
53580
+ const targetUrl = new URL(guard.targetUrl);
53581
+ if (sourceUrl.startsWith("ssh://")) {
53582
+ const source = new URL(sourceUrl);
53583
+ if (source.hostname === targetUrl.hostname) {
53584
+ source.pathname = targetUrl.pathname;
53585
+ targetTransport = source.toString();
53586
+ }
53587
+ } else {
53588
+ const source = /^([^@]+@([^:]+)):/.exec(sourceUrl);
53589
+ if (source?.[2] === targetUrl.hostname)
53590
+ targetTransport = `${source[1]}:${targetUrl.pathname.replace(/^\//, "")}`;
53591
+ }
53592
+ }
53593
+ await run(
53594
+ [
53595
+ "fetch",
53596
+ "--no-tags",
53597
+ "--no-write-fetch-head",
53598
+ "--",
53599
+ targetTransport,
53600
+ guard.targetSha
53601
+ ],
53602
+ "Could not fetch the merge target"
53603
+ );
53604
+ const syncedTargetSha = guard.targetSha;
53605
+ const ancestor = await git4(
53606
+ folder,
53607
+ ["merge-base", "--is-ancestor", syncedTargetSha, "HEAD"],
53608
+ timeoutMs,
53609
+ signal2
53610
+ );
53611
+ if (ancestor.exitCode !== 0 && ancestor.exitCode !== 1)
53612
+ throw new Error("Could not compare the merge target");
53613
+ const result = async (outcome) => ({
53614
+ ...successfulProcess9,
53615
+ outcome,
53616
+ previousHeadSha: guard.expectedHeadSha,
53617
+ syncedHeadSha: await head(),
53618
+ syncedTargetSha,
53619
+ checkedAt: (/* @__PURE__ */ new Date()).toISOString(),
53620
+ preparations: await executeWorktreePreparations(
53621
+ folder,
53622
+ input.preparations,
53623
+ "INSPECT",
53624
+ timeoutMs,
53625
+ signal2
53626
+ ),
53627
+ worktree: await inspectWorktreeItem(
53628
+ folder,
53629
+ folder,
53630
+ input.baseBranch,
53631
+ false,
53632
+ Math.min(timeoutMs, 3e4),
53633
+ signal2
53634
+ )
53635
+ });
53636
+ if (await head() !== guard.expectedHeadSha)
53637
+ throw new Error("The worktree source commit changed during sync");
53638
+ await clean2();
53639
+ if (ancestor.exitCode === 0) return result("NO_CHANGE");
53640
+ const paths = await preparationConflictPaths(
53641
+ folder,
53642
+ input.preparations,
53643
+ syncedTargetSha,
53644
+ timeoutMs,
53645
+ signal2
53646
+ );
53647
+ if (paths.length)
53648
+ return {
53649
+ ...await result("PREPARATION_CONFLICT"),
53650
+ preparationConflictPaths: paths
53651
+ };
53652
+ const rebase = await git4(
53653
+ folder,
53654
+ ["rebase", syncedTargetSha],
53655
+ timeoutMs,
53656
+ signal2
53657
+ );
53658
+ if (rebase.exitCode !== 0) {
53659
+ if (await rebaseInProgress(folder, timeoutMs, signal2))
53660
+ return result("CONFLICT");
53661
+ throw new Error(cleanError4(rebase.stderr || "Auto Merge rebase failed"));
53662
+ }
53663
+ const syncedHeadSha = await head();
53664
+ await clean2();
53665
+ await run(
53666
+ [
53667
+ "push",
53668
+ `--force-with-lease=refs/heads/${input.expectedBranch}:${guard.expectedHeadSha}`,
53669
+ "origin",
53670
+ `${syncedHeadSha}:refs/heads/${input.expectedBranch}`
53671
+ ],
53672
+ "Auto Merge push failed; review the local and remote revisions before resuming"
53673
+ );
53674
+ if (await head() !== syncedHeadSha)
53675
+ throw new Error("The worktree source commit changed during push");
53676
+ return result("SYNCED");
53677
+ }
53365
53678
  var autoSyncWorktree = async (payload, timeoutMs, signal2) => {
53366
53679
  const input = worktreeAutoSyncJobPayload(payload);
53367
53680
  const folder = await validateWorktree2(input, timeoutMs, signal2);
@@ -53398,6 +53711,8 @@ var autoSyncWorktree = async (payload, timeoutMs, signal2) => {
53398
53711
  `Auto Sync expected branch ${input.expectedBranch}, but found ${branch ?? "detached HEAD"}`
53399
53712
  );
53400
53713
  }
53714
+ if (input.mergeGuard)
53715
+ return syncReviewedBranch(input, folder, timeoutMs, signal2);
53401
53716
  const upstreamResult = branch ? await git4(
53402
53717
  folder,
53403
53718
  ["rev-parse", "--abbrev-ref", "@{upstream}"],
@@ -54134,6 +54449,11 @@ var JobExecutor = class {
54134
54449
  controller.signal
54135
54450
  ),
54136
54451
  reportBuildProgress: (input) => this.client.reportBuildProgress(input),
54452
+ reportBuildDeploymentLaunched: (deploymentId, launchedAt) => this.client.reportBuildDeploymentLaunched(
54453
+ claimed.id,
54454
+ deploymentId,
54455
+ launchedAt
54456
+ ),
54137
54457
  appendBuildLogChunks: (buildId, chunks) => this.client.appendBuildLogChunks(buildId, chunks),
54138
54458
  uploadBuildArtifact: (input) => this.client.uploadBuildArtifact(input),
54139
54459
  uploadBuildArtifactTransfer: (input) => this.client.uploadBuildArtifactTransfer(input),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-development-environment/control-agent",
3
- "version": "0.0.134",
3
+ "version": "0.0.136",
4
4
  "description": "Control agent for the AI Development Environment",
5
5
  "license": "MIT",
6
6
  "repository": {