@ai-development-environment/control-agent 0.0.137 → 0.0.139

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 +468 -191
  2. package/package.json +1 -1
@@ -7130,25 +7130,38 @@ var AgentGraphQLClient = class {
7130
7130
  );
7131
7131
  }
7132
7132
  async uploadBuildArtifact(input) {
7133
+ const timeoutMs = input.timeoutMs ?? this.requestTimeoutMs;
7134
+ if (timeoutMs <= 0)
7135
+ throw new DOMException("Artifact upload timed out", "TimeoutError");
7136
+ const timeout = AbortSignal.timeout(timeoutMs);
7137
+ const signal2 = input.signal ? AbortSignal.any([input.signal, timeout]) : timeout;
7138
+ signal2.throwIfAborted();
7133
7139
  const information = await stat(input.path);
7134
- const response = await fetch(
7135
- `${this.server}/api/build-artifact-uploads/${encodeURIComponent(input.uploadId)}`,
7136
- {
7137
- method: "POST",
7138
- headers: mergeHeaders(this.headers, {
7139
- ...this.credential ? { authorization: `Bearer ${this.credential}` } : {},
7140
- "content-length": String(information.size),
7141
- "content-type": input.contentType,
7142
- "x-artifact-filename": encodeURIComponent(input.filename)
7143
- }),
7144
- body: Readable.toWeb(createReadStream(input.path)),
7145
- duplex: "half"
7146
- }
7147
- );
7148
- if (!response.ok) {
7149
- throw new Error(
7150
- `Artifact upload failed: HTTP ${response.status} ${await response.text()}`
7140
+ signal2.throwIfAborted();
7141
+ const source = createReadStream(input.path);
7142
+ try {
7143
+ const response = await fetch(
7144
+ `${this.server}/api/build-artifact-uploads/${encodeURIComponent(input.uploadId)}`,
7145
+ {
7146
+ method: "POST",
7147
+ headers: mergeHeaders(this.headers, {
7148
+ ...this.credential ? { authorization: `Bearer ${this.credential}` } : {},
7149
+ "content-length": String(information.size),
7150
+ "content-type": input.contentType,
7151
+ "x-artifact-filename": encodeURIComponent(input.filename)
7152
+ }),
7153
+ body: Readable.toWeb(source),
7154
+ duplex: "half",
7155
+ signal: signal2
7156
+ }
7151
7157
  );
7158
+ if (!response.ok) {
7159
+ throw new Error(
7160
+ `Artifact upload failed: HTTP ${response.status} ${await response.text()}`
7161
+ );
7162
+ }
7163
+ } finally {
7164
+ source.destroy();
7152
7165
  }
7153
7166
  }
7154
7167
  artifactTransferHeaders(values = {}) {
@@ -7471,12 +7484,12 @@ var AgentGraphQLClient = class {
7471
7484
  );
7472
7485
  return data.beginRunAttempt;
7473
7486
  }
7474
- updateRunAttemptNativeId(attemptId, nativeId, providerVersion) {
7487
+ updateRunAttemptNativeId(attemptId, nativeId, providerVersion, sessionInfo) {
7475
7488
  return this.request(
7476
- `mutation UpdateRunAttemptNativeId($attemptId: ID!, $nativeId: String!, $providerVersion: String) {
7477
- updateRunAttemptNativeId(attemptId: $attemptId, nativeId: $nativeId, providerVersion: $providerVersion) { id nativeId }
7489
+ `mutation UpdateRunAttemptNativeId($attemptId: ID!, $nativeId: String!, $providerVersion: String, $sessionInfo: RunSessionInfoInput) {
7490
+ updateRunAttemptNativeId(attemptId: $attemptId, nativeId: $nativeId, providerVersion: $providerVersion, sessionInfo: $sessionInfo) { id nativeId }
7478
7491
  }`,
7479
- { attemptId, nativeId, providerVersion }
7492
+ { attemptId, nativeId, providerVersion, sessionInfo }
7480
7493
  );
7481
7494
  }
7482
7495
  appendRunEvents(runId, attemptId, events) {
@@ -9301,7 +9314,7 @@ function worktreeGitInspectPayload(value) {
9301
9314
  `Unexpected worktree Git inspect payload field: ${unexpected}`
9302
9315
  );
9303
9316
  }
9304
- if (payload.action !== "STATE" && payload.action !== "STASH_DIFF") {
9317
+ if (payload.action !== "STATE" && payload.action !== "REFRESH" && payload.action !== "STASH_DIFF") {
9305
9318
  throw new Error("worktree Git inspect payload.action is invalid");
9306
9319
  }
9307
9320
  const common = {
@@ -9319,9 +9332,9 @@ function worktreeGitInspectPayload(value) {
9319
9332
  "worktree Git inspect payload.expectedOrigin"
9320
9333
  )
9321
9334
  };
9322
- if (payload.action === "STATE") {
9335
+ if (payload.action === "STATE" || payload.action === "REFRESH") {
9323
9336
  if (payload.stashOid !== void 0) {
9324
- throw new Error("STATE inspection cannot include stashOid");
9337
+ throw new Error(`${payload.action} inspection cannot include stashOid`);
9325
9338
  }
9326
9339
  return { action: payload.action, ...common };
9327
9340
  }
@@ -11552,7 +11565,7 @@ function tailscaleServeRemovePayload(value) {
11552
11565
  // package.json
11553
11566
  var package_default = {
11554
11567
  name: "@ai-development-environment/control-agent",
11555
- version: "0.0.137",
11568
+ version: "0.0.139",
11556
11569
  description: "Control agent for the AI Development Environment",
11557
11570
  license: "MIT",
11558
11571
  repository: {
@@ -12060,6 +12073,17 @@ function repairToolPath(options = {}) {
12060
12073
 
12061
12074
  // src/runs/codex-adapter.ts
12062
12075
  var execFileAsync = promisify(execFile);
12076
+ async function codexSessionInfo(thread, cwd) {
12077
+ const path = typeof thread.path === "string" && thread.path ? thread.path : await findCodexSessionFile(
12078
+ String(thread.id),
12079
+ homedir4(),
12080
+ process.env.CODEX_HOME
12081
+ ).catch(() => null);
12082
+ return {
12083
+ workingDirectory: typeof thread.cwd === "string" && thread.cwd ? thread.cwd : cwd,
12084
+ ...path ? { nativeTranscriptPath: path } : {}
12085
+ };
12086
+ }
12063
12087
  function codexAppServerArgs(modelCatalogPath) {
12064
12088
  return [
12065
12089
  "app-server",
@@ -12453,7 +12477,8 @@ var CodexAdapter = class {
12453
12477
  throw new Error("Codex did not return a thread ID");
12454
12478
  await callbacks.onNativeId(
12455
12479
  nativeId,
12456
- typeof thread.cliVersion === "string" ? thread.cliVersion : void 0
12480
+ typeof thread.cliVersion === "string" ? thread.cliVersion : void 0,
12481
+ await codexSessionInfo(thread, cwd)
12457
12482
  );
12458
12483
  let turnId = "";
12459
12484
  let stopReason = null;
@@ -12618,6 +12643,7 @@ var CodexAdapter = class {
12618
12643
  const thread = asRecord(value);
12619
12644
  const updatedAtKey = String(thread.updatedAt ?? "");
12620
12645
  const cached = this.hydrationCache.get(String(thread.id));
12646
+ const sessionInfo = await codexSessionInfo(thread, worktree.folder);
12621
12647
  let history = {};
12622
12648
  const active = asRecord(thread.status).type === "active";
12623
12649
  if (active) this.hydrationCache.delete(String(thread.id));
@@ -12640,11 +12666,7 @@ var CodexAdapter = class {
12640
12666
  } catch {
12641
12667
  }
12642
12668
  try {
12643
- const path = typeof thread.path === "string" ? thread.path : await findCodexSessionFile(
12644
- String(thread.id),
12645
- homedir4(),
12646
- process.env.CODEX_HOME
12647
- );
12669
+ const path = sessionInfo.nativeTranscriptPath;
12648
12670
  if (path)
12649
12671
  Object.assign(
12650
12672
  history,
@@ -12671,7 +12693,8 @@ var CodexAdapter = class {
12671
12693
  branch: firstString(asRecord(thread.gitInfo).branch) || worktree.branch || void 0,
12672
12694
  createdAt: thread.createdAt ? new Date(Number(thread.createdAt) * 1e3).toISOString() : void 0,
12673
12695
  updatedAt: thread.updatedAt ? new Date(Number(thread.updatedAt) * 1e3).toISOString() : void 0,
12674
- rawMetadata: thread
12696
+ rawMetadata: thread,
12697
+ sessionInfo
12675
12698
  });
12676
12699
  }
12677
12700
  cursor = typeof page.nextCursor === "string" ? page.nextCursor : null;
@@ -42895,7 +42918,15 @@ var ClaudeAdapter = class {
42895
42918
  if (sessionId && sessionId !== nativeId) {
42896
42919
  nativeId = sessionId;
42897
42920
  const version = record.subtype === "init" && typeof record.claude_code_version === "string" ? record.claude_code_version : void 0;
42898
- await callbacks.onNativeId(sessionId, version);
42921
+ const path = await findClaudeSessionFile(
42922
+ sessionId,
42923
+ homedir5(),
42924
+ process.env.CLAUDE_CONFIG_DIR
42925
+ ).catch(() => null);
42926
+ await callbacks.onNativeId(sessionId, version, {
42927
+ workingDirectory: typeof record.cwd === "string" && record.cwd ? record.cwd : input.run.worktree.folder,
42928
+ ...path ? { nativeTranscriptPath: path } : {}
42929
+ });
42899
42930
  }
42900
42931
  const messageText = firstString(
42901
42932
  record.message ?? record.result ?? record.summary
@@ -42976,16 +43007,20 @@ var ClaudeAdapter = class {
42976
43007
  const lastModifiedKey = `${session.lastModified ?? ""}:${session.fileSize ?? ""}`;
42977
43008
  const cached = this.hydrationCache.get(session.sessionId);
42978
43009
  let history = {};
43010
+ const path = await findClaudeSessionFile(
43011
+ session.sessionId,
43012
+ homedir5(),
43013
+ process.env.CLAUDE_CONFIG_DIR
43014
+ ).catch(() => null);
43015
+ const sessionInfo = {
43016
+ workingDirectory: session.cwd || worktree.folder,
43017
+ ...path ? { nativeTranscriptPath: path } : {}
43018
+ };
42979
43019
  if (cached && cached.lastModified === lastModifiedKey) {
42980
43020
  history = cached.history;
42981
43021
  } else {
42982
43022
  let transcript = [];
42983
43023
  try {
42984
- const path = await findClaudeSessionFile(
42985
- session.sessionId,
42986
- homedir5(),
42987
- process.env.CLAUDE_CONFIG_DIR
42988
- );
42989
43024
  if (path) transcript = await readClaudeTranscript(path);
42990
43025
  history = claudeTranscriptMetadata(transcript);
42991
43026
  } catch {
@@ -43016,7 +43051,8 @@ var ClaudeAdapter = class {
43016
43051
  branch: session.gitBranch || worktree.branch || void 0,
43017
43052
  createdAt: session.createdAt ? new Date(session.createdAt).toISOString() : void 0,
43018
43053
  updatedAt: new Date(session.lastModified).toISOString(),
43019
- rawMetadata: session
43054
+ rawMetadata: session,
43055
+ sessionInfo
43020
43056
  });
43021
43057
  }
43022
43058
  }
@@ -43788,6 +43824,7 @@ function captureCommand(options) {
43788
43824
  let timedOut = false;
43789
43825
  let cancelled = false;
43790
43826
  let settled = false;
43827
+ let terminating = false;
43791
43828
  let killTimer = null;
43792
43829
  const detached = process.platform !== "win32";
43793
43830
  const child = spawn3(options.command, options.args, {
@@ -43842,10 +43879,11 @@ function captureCommand(options) {
43842
43879
  }
43843
43880
  };
43844
43881
  const terminate2 = () => {
43845
- if (child.exitCode !== null || child.killed) return;
43882
+ if (settled || terminating) return;
43883
+ terminating = true;
43846
43884
  sendSignal("SIGTERM");
43847
43885
  killTimer = setTimeout(() => {
43848
- if (child.exitCode === null) sendSignal("SIGKILL");
43886
+ if (!settled) sendSignal("SIGKILL");
43849
43887
  }, 5e3);
43850
43888
  killTimer.unref();
43851
43889
  };
@@ -45298,21 +45336,148 @@ import {
45298
45336
 
45299
45337
  // src/repository-coordinator.ts
45300
45338
  var RepositoryCoordinator = class {
45301
- tails = /* @__PURE__ */ new Map();
45302
- async run(key, task) {
45303
- const previous = this.tails.get(key) ?? Promise.resolve();
45304
- let release2;
45305
- const current = new Promise((resolve11) => {
45306
- release2 = resolve11;
45339
+ queues = /* @__PURE__ */ new Map();
45340
+ run(key, task, signal2) {
45341
+ return this.enqueue(key, "exclusive", () => task(), signal2);
45342
+ }
45343
+ /** Keep source files stable during builds while allowing live inspections. */
45344
+ runBuild(key, task, signal2) {
45345
+ return this.enqueue(key, "build", () => task(), signal2);
45346
+ }
45347
+ /** Read-only worktree inspections may overlap builds and reconciliation. */
45348
+ runInspection(key, task, signal2) {
45349
+ return this.enqueue(key, "inspection", () => task(), signal2);
45350
+ }
45351
+ /** Keep reconciliation and its reports ordered with all user mutations. */
45352
+ runReconciliation(key, task, signal2) {
45353
+ return this.enqueue(
45354
+ key,
45355
+ "reconciliation",
45356
+ (queue) => task({
45357
+ runExclusive: (exclusiveTask) => this.runReconciliationExclusive(key, queue, exclusiveTask)
45358
+ }),
45359
+ signal2
45360
+ );
45361
+ }
45362
+ runReconciliationExclusive(key, queue, task) {
45363
+ if (!queue.reconciliation || queue.exclusiveReconciliationPhase) {
45364
+ return Promise.reject(new Error("Invalid reconciliation phase"));
45365
+ }
45366
+ return new Promise((resolve11, reject) => {
45367
+ const complete = () => {
45368
+ queue.exclusiveReconciliationPhase = null;
45369
+ queue.exclusive = false;
45370
+ this.drain(key, queue);
45371
+ };
45372
+ queue.exclusiveReconciliationPhase = () => {
45373
+ void Promise.resolve().then(task).then(
45374
+ (result) => {
45375
+ complete();
45376
+ resolve11(result);
45377
+ },
45378
+ (error) => {
45379
+ complete();
45380
+ reject(error);
45381
+ }
45382
+ );
45383
+ };
45384
+ this.drain(key, queue);
45307
45385
  });
45308
- const tail = previous.then(() => current);
45309
- this.tails.set(key, tail);
45310
- await previous;
45311
- try {
45312
- return await task();
45313
- } finally {
45314
- release2();
45315
- if (this.tails.get(key) === tail) this.tails.delete(key);
45386
+ }
45387
+ enqueue(key, kind, task, signal2) {
45388
+ if (signal2?.aborted) return Promise.reject(signal2.reason);
45389
+ let queue = this.queues.get(key);
45390
+ if (!queue) {
45391
+ queue = {
45392
+ inspections: 0,
45393
+ reconciliation: false,
45394
+ exclusive: false,
45395
+ build: false,
45396
+ exclusiveReconciliationPhase: null,
45397
+ pending: []
45398
+ };
45399
+ this.queues.set(key, queue);
45400
+ }
45401
+ const repositoryQueue = queue;
45402
+ return new Promise((resolve11, reject) => {
45403
+ let pending = true;
45404
+ const complete = () => {
45405
+ if (kind === "inspection") repositoryQueue.inspections -= 1;
45406
+ else if (kind === "reconciliation")
45407
+ repositoryQueue.reconciliation = false;
45408
+ else if (kind === "build") repositoryQueue.build = false;
45409
+ else repositoryQueue.exclusive = false;
45410
+ this.drain(key, repositoryQueue);
45411
+ };
45412
+ const abort = () => {
45413
+ if (!pending) return;
45414
+ pending = false;
45415
+ const index = repositoryQueue.pending.indexOf(operation);
45416
+ if (index !== -1) repositoryQueue.pending.splice(index, 1);
45417
+ signal2?.removeEventListener("abort", abort);
45418
+ reject(signal2?.reason);
45419
+ this.drain(key, repositoryQueue);
45420
+ };
45421
+ const operation = {
45422
+ kind,
45423
+ start: () => {
45424
+ pending = false;
45425
+ signal2?.removeEventListener("abort", abort);
45426
+ void Promise.resolve().then(() => {
45427
+ signal2?.throwIfAborted();
45428
+ return task(repositoryQueue);
45429
+ }).then(
45430
+ (result) => {
45431
+ complete();
45432
+ resolve11(result);
45433
+ },
45434
+ (error) => {
45435
+ complete();
45436
+ reject(error);
45437
+ }
45438
+ );
45439
+ }
45440
+ };
45441
+ repositoryQueue.pending.push(operation);
45442
+ signal2?.addEventListener("abort", abort, { once: true });
45443
+ if (signal2?.aborted) abort();
45444
+ else this.drain(key, repositoryQueue);
45445
+ });
45446
+ }
45447
+ drain(key, queue) {
45448
+ if (queue.exclusiveReconciliationPhase) {
45449
+ if (!queue.exclusive && queue.inspections === 0) {
45450
+ queue.exclusive = true;
45451
+ queue.exclusiveReconciliationPhase();
45452
+ }
45453
+ return;
45454
+ }
45455
+ while (!queue.exclusive && !queue.build && queue.pending.length) {
45456
+ const next = queue.pending[0];
45457
+ if (next.kind === "exclusive" && (queue.inspections > 0 || queue.reconciliation) || next.kind === "build" && queue.reconciliation || next.kind === "reconciliation" && queue.reconciliation) {
45458
+ return;
45459
+ }
45460
+ queue.pending.shift();
45461
+ if (next.kind === "inspection") queue.inspections += 1;
45462
+ else if (next.kind === "reconciliation") queue.reconciliation = true;
45463
+ else if (next.kind === "build") queue.build = true;
45464
+ else queue.exclusive = true;
45465
+ next.start();
45466
+ }
45467
+ if (queue.build) {
45468
+ const inspections = queue.pending.filter(
45469
+ (operation) => operation.kind === "inspection"
45470
+ );
45471
+ queue.pending = queue.pending.filter(
45472
+ (operation) => operation.kind !== "inspection"
45473
+ );
45474
+ for (const inspection of inspections) {
45475
+ queue.inspections += 1;
45476
+ inspection.start();
45477
+ }
45478
+ }
45479
+ if (!queue.exclusive && !queue.build && !queue.reconciliation && queue.inspections === 0 && queue.pending.length === 0) {
45480
+ this.queues.delete(key);
45316
45481
  }
45317
45482
  }
45318
45483
  };
@@ -48322,7 +48487,9 @@ var downloadIosBuildArtifact = async (payload, timeoutMs, signal2, onLog, contex
48322
48487
  uploadId: input.uploadId,
48323
48488
  path: uploadPath,
48324
48489
  filename,
48325
- contentType
48490
+ contentType,
48491
+ signal: signal2,
48492
+ timeoutMs
48326
48493
  });
48327
48494
  return successfulProcess3;
48328
48495
  } finally {
@@ -51028,6 +51195,17 @@ async function compareGitCheckpoint(cwd, target2, current) {
51028
51195
  ],
51029
51196
  PATCH_LIMIT
51030
51197
  );
51198
+ const worktreeSummary = await cappedGit(
51199
+ cwd,
51200
+ [
51201
+ "diff",
51202
+ "--stat",
51203
+ "--find-renames",
51204
+ target2.worktreeTree,
51205
+ current.worktreeTree
51206
+ ],
51207
+ SUMMARY_LIMIT
51208
+ );
51031
51209
  const indexPatch = target2.indexTree && current.indexTree ? await cappedGit(
51032
51210
  cwd,
51033
51211
  [
@@ -51048,6 +51226,8 @@ async function compareGitCheckpoint(cwd, target2, current) {
51048
51226
  ])
51049
51227
  ) : 0;
51050
51228
  return {
51229
+ worktreePatch,
51230
+ worktreeSummary,
51051
51231
  rollbackPatch: [
51052
51232
  worktreePatch && `Worktree changes that will be undone:
51053
51233
 
@@ -51844,6 +52024,62 @@ async function rebaseInProgress(folder, timeoutMs, signal2) {
51844
52024
  }
51845
52025
  return false;
51846
52026
  }
52027
+ async function gitStatePathExists(folder, name, timeoutMs, signal2) {
52028
+ const result = requireSuccess4(
52029
+ await git4(
52030
+ folder,
52031
+ ["rev-parse", "--path-format=absolute", "--git-path", name],
52032
+ timeoutMs,
52033
+ signal2
52034
+ ),
52035
+ `Could not resolve Git state path ${name}`
52036
+ );
52037
+ const candidate = result.stdout.trim();
52038
+ if (!candidate || result.outputTruncated) {
52039
+ throw new Error(`Could not resolve Git state path ${name}`);
52040
+ }
52041
+ try {
52042
+ await stat11(candidate);
52043
+ return true;
52044
+ } catch (error) {
52045
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") {
52046
+ return false;
52047
+ }
52048
+ throw error;
52049
+ }
52050
+ }
52051
+ async function inspectWorktreeRefreshState(folder, timeoutMs, signal2) {
52052
+ const [status2, rebaseMerge, rebaseApply, mergeInProgress] = await Promise.all(
52053
+ [
52054
+ git4(
52055
+ folder,
52056
+ [
52057
+ "--no-optional-locks",
52058
+ "status",
52059
+ "--porcelain=v1",
52060
+ "-z",
52061
+ "--untracked-files=all",
52062
+ "--ignore-submodules=none"
52063
+ ],
52064
+ timeoutMs,
52065
+ signal2
52066
+ ),
52067
+ gitStatePathExists(folder, "rebase-merge", timeoutMs, signal2),
52068
+ gitStatePathExists(folder, "rebase-apply", timeoutMs, signal2),
52069
+ gitStatePathExists(folder, "MERGE_HEAD", timeoutMs, signal2)
52070
+ ]
52071
+ );
52072
+ requireSuccess4(status2, "Could not refresh worktree Git status");
52073
+ if (status2.outputTruncated) {
52074
+ throw new Error("Worktree Git status exceeded the output limit");
52075
+ }
52076
+ return {
52077
+ dirty: Boolean(status2.stdout),
52078
+ hasConflicts: statusChangeState(status2.stdout).hasConflicts,
52079
+ rebaseInProgress: rebaseMerge || rebaseApply,
52080
+ mergeInProgress
52081
+ };
52082
+ }
51847
52083
  async function rebaseBranch(folder, timeoutMs, signal2) {
51848
52084
  for (const name of ["rebase-merge/head-name", "rebase-apply/head-name"]) {
51849
52085
  const pathResult = await git4(
@@ -52900,7 +53136,9 @@ var downloadWorktreeDiffAsset = async (payload, timeoutMs, signal2, _onLog, cont
52900
53136
  uploadId: input.uploadId,
52901
53137
  path: uploadPath,
52902
53138
  filename: basename5(input.path),
52903
- contentType: imageContentType(input.path)
53139
+ contentType: imageContentType(input.path),
53140
+ signal: signal2,
53141
+ timeoutMs
52904
53142
  });
52905
53143
  return successfulProcess9;
52906
53144
  } finally {
@@ -52982,10 +53220,10 @@ var inspectWorktree = async (payload, timeoutMs, signal2) => {
52982
53220
  var inspectWorktreeGit = async (payload, timeoutMs, signal2) => {
52983
53221
  const input = worktreeGitInspectPayload(payload);
52984
53222
  const folder = await validateWorktree2(input, timeoutMs, signal2);
52985
- if (input.action === "STATE") {
53223
+ if (input.action !== "STASH_DIFF") {
52986
53224
  return {
52987
53225
  ...successfulProcess9,
52988
- state: await inspectCodebaseGitState(folder, timeoutMs, signal2)
53226
+ state: input.action === "REFRESH" ? await inspectWorktreeRefreshState(folder, timeoutMs, signal2) : await inspectCodebaseGitState(folder, timeoutMs, signal2)
52989
53227
  };
52990
53228
  }
52991
53229
  return {
@@ -55146,39 +55384,61 @@ var JobExecutor = class {
55146
55384
  const handler = handlers[claimed.kind];
55147
55385
  if (!handler)
55148
55386
  throw new Error(`No local handler is registered for ${claimed.kind}`);
55149
- const runHandler = () => handler(
55150
- claimed.payload,
55151
- claimed.timeoutSeconds * 1e3,
55152
- controller.signal,
55153
- (log2) => this.client.appendLog(claimed.id, log2).then(() => void 0),
55154
- {
55155
- agentId: claimed.agentId,
55156
- reportBranchDeletionResult: (result2) => this.client.reportBranchDeletionResult(claimed.id, result2),
55157
- reportWorktreeActivity: (input) => this.client.reportWorktreeActivity(input),
55158
- refreshFetchedCodebase: (input) => refreshFetchedCodebase(
55159
- this.client,
55160
- input,
55161
- claimed.timeoutSeconds * 1e3,
55162
- controller.signal
55163
- ),
55164
- reportBuildProgress: (input) => this.client.reportBuildProgress(input),
55165
- reportBuildDeploymentLaunched: (deploymentId, launchedAt) => this.client.reportBuildDeploymentLaunched(
55166
- claimed.id,
55167
- deploymentId,
55168
- launchedAt
55169
- ),
55170
- appendBuildLogChunks: (buildId, chunks) => this.client.appendBuildLogChunks(buildId, chunks),
55171
- uploadBuildArtifact: (input) => this.client.uploadBuildArtifact(input),
55172
- uploadBuildArtifactTransfer: (input) => this.client.uploadBuildArtifactTransfer(input),
55173
- downloadBuildArtifactTransfer: (input) => this.client.downloadBuildArtifactTransfer(input),
55174
- downloadDsymDwarf: (input) => this.client.downloadDsymDwarf(input),
55175
- claimSigningSecretTransfer: (transferId) => this.client.claimSigningSecretTransfer(transferId),
55176
- claimWorkflowJobSecrets: () => this.client.claimWorkflowJobSecrets(claimed.id),
55177
- appendCommandOutput: (attemptId, chunks) => this.client.appendCommandOutput(claimed.id, attemptId, chunks)
55178
- }
55179
- );
55387
+ const runHandler = () => {
55388
+ controller.signal.throwIfAborted();
55389
+ return handler(
55390
+ claimed.payload,
55391
+ claimed.timeoutSeconds * 1e3,
55392
+ controller.signal,
55393
+ (log2) => this.client.appendLog(claimed.id, log2).then(() => void 0),
55394
+ {
55395
+ agentId: claimed.agentId,
55396
+ reportBranchDeletionResult: (result2) => this.client.reportBranchDeletionResult(claimed.id, result2),
55397
+ reportWorktreeActivity: (input) => this.client.reportWorktreeActivity(input),
55398
+ refreshFetchedCodebase: (input) => refreshFetchedCodebase(
55399
+ this.client,
55400
+ input,
55401
+ claimed.timeoutSeconds * 1e3,
55402
+ controller.signal
55403
+ ),
55404
+ reportBuildProgress: (input) => this.client.reportBuildProgress(input),
55405
+ reportBuildDeploymentLaunched: (deploymentId, launchedAt) => this.client.reportBuildDeploymentLaunched(
55406
+ claimed.id,
55407
+ deploymentId,
55408
+ launchedAt
55409
+ ),
55410
+ appendBuildLogChunks: (buildId, chunks) => this.client.appendBuildLogChunks(buildId, chunks),
55411
+ uploadBuildArtifact: (input) => this.client.uploadBuildArtifact(input),
55412
+ uploadBuildArtifactTransfer: (input) => this.client.uploadBuildArtifactTransfer(input),
55413
+ downloadBuildArtifactTransfer: (input) => this.client.downloadBuildArtifactTransfer(input),
55414
+ downloadDsymDwarf: (input) => this.client.downloadDsymDwarf(input),
55415
+ claimSigningSecretTransfer: (transferId) => this.client.claimSigningSecretTransfer(transferId),
55416
+ claimWorkflowJobSecrets: () => this.client.claimWorkflowJobSecrets(claimed.id),
55417
+ appendCommandOutput: (attemptId, chunks) => this.client.appendCommandOutput(claimed.id, attemptId, chunks)
55418
+ }
55419
+ );
55420
+ };
55180
55421
  const codebaseId = claimed.payload && typeof claimed.payload === "object" && !Array.isArray(claimed.payload) && typeof claimed.payload.codebaseId === "string" ? String(claimed.payload.codebaseId) : null;
55181
- result = codebaseId ? await this.repositoryCoordinator.run(codebaseId, runHandler) : await runHandler();
55422
+ if (!codebaseId) result = await runHandler();
55423
+ else if (claimed.kind === IOS_BUILD_JOB_KIND) {
55424
+ result = await this.repositoryCoordinator.runBuild(
55425
+ codebaseId,
55426
+ runHandler,
55427
+ controller.signal
55428
+ );
55429
+ } else if (claimed.kind === WORKTREE_INSPECT_JOB_KIND || claimed.kind === WORKTREE_DIFF_JOB_KIND || claimed.kind === WORKTREE_DIFF_ASSET_JOB_KIND || claimed.kind === WORKTREE_GIT_INSPECT_JOB_KIND || claimed.kind === CODEBASE_GIT_INSPECT_JOB_KIND) {
55430
+ result = await this.repositoryCoordinator.runInspection(
55431
+ codebaseId,
55432
+ runHandler,
55433
+ controller.signal
55434
+ );
55435
+ } else {
55436
+ result = await this.repositoryCoordinator.run(
55437
+ codebaseId,
55438
+ runHandler,
55439
+ controller.signal
55440
+ );
55441
+ }
55182
55442
  status2 = result.cancelled ? "CANCELLED" : result.timedOut ? "TIMED_OUT" : result.exitCode === 0 ? "SUCCEEDED" : "FAILED";
55183
55443
  completionError = status2 === "FAILED" ? `Process exited with code ${result.exitCode}` : void 0;
55184
55444
  } catch (error) {
@@ -55244,110 +55504,118 @@ var CodebaseMonitor = class {
55244
55504
  const batch = codebases.slice(index, index + CONCURRENCY2);
55245
55505
  await Promise.all(
55246
55506
  batch.map(
55247
- (codebase) => this.repositoryCoordinator.run(codebase.id, async () => {
55248
- let snapshot = await inspectCodebase(
55249
- codebase.folder,
55250
- INSPECTION_TIMEOUT_MS,
55251
- signal2,
55252
- codebase.canonicalOrigin
55253
- );
55254
- let fetchAttemptedAt = null;
55255
- let fetchError = null;
55256
- const fetchedAt2 = Math.max(
55257
- snapshot.fetchedAt ? new Date(snapshot.fetchedAt).getTime() : 0,
55258
- codebase.lastFetchAttemptAt ? new Date(codebase.lastFetchAttemptAt).getTime() : 0
55259
- );
55260
- const fetchDue = snapshot.availability === "AVAILABLE" && Date.now() - fetchedAt2 >= configuration.fetchIntervalSeconds * 1e3;
55261
- if (fetchDue && !signal2.aborted) {
55262
- fetchAttemptedAt = (/* @__PURE__ */ new Date()).toISOString();
55263
- const fetchResult = await captureCommand({
55264
- command: "git",
55265
- args: ["-C", codebase.folder, "fetch", "origin"],
55266
- timeoutMs: 3e5,
55267
- signal: signal2,
55268
- env: {
55269
- ...process.env,
55270
- GIT_TERMINAL_PROMPT: "0",
55271
- GIT_OPTIONAL_LOCKS: "0"
55272
- }
55273
- });
55274
- if (fetchResult.exitCode === 0) {
55275
- if (codebase.keepBaseBranchUpToDate && codebase.defaultBranch) {
55276
- await updateBaseBranchAfterFetch(
55507
+ (codebase) => this.repositoryCoordinator.runReconciliation(
55508
+ codebase.id,
55509
+ async ({ runExclusive }) => {
55510
+ let snapshot = await inspectCodebase(
55511
+ codebase.folder,
55512
+ INSPECTION_TIMEOUT_MS,
55513
+ signal2,
55514
+ codebase.canonicalOrigin
55515
+ );
55516
+ let fetchAttemptedAt = null;
55517
+ let fetchError = null;
55518
+ const fetchedAt2 = Math.max(
55519
+ snapshot.fetchedAt ? new Date(snapshot.fetchedAt).getTime() : 0,
55520
+ codebase.lastFetchAttemptAt ? new Date(codebase.lastFetchAttemptAt).getTime() : 0
55521
+ );
55522
+ const fetchDue = snapshot.availability === "AVAILABLE" && Date.now() - fetchedAt2 >= configuration.fetchIntervalSeconds * 1e3;
55523
+ if (fetchDue && !signal2.aborted) {
55524
+ fetchAttemptedAt = (/* @__PURE__ */ new Date()).toISOString();
55525
+ const fetchResult = await captureCommand({
55526
+ command: "git",
55527
+ args: ["-C", codebase.folder, "fetch", "origin"],
55528
+ timeoutMs: 3e5,
55529
+ signal: signal2,
55530
+ env: {
55531
+ ...process.env,
55532
+ GIT_TERMINAL_PROMPT: "0",
55533
+ GIT_OPTIONAL_LOCKS: "0"
55534
+ }
55535
+ });
55536
+ if (fetchResult.exitCode === 0) {
55537
+ if (codebase.keepBaseBranchUpToDate && codebase.defaultBranch) {
55538
+ const baseBranch = codebase.defaultBranch;
55539
+ await runExclusive(
55540
+ () => updateBaseBranchAfterFetch(
55541
+ codebase.folder,
55542
+ baseBranch,
55543
+ INSPECTION_TIMEOUT_MS,
55544
+ signal2
55545
+ )
55546
+ );
55547
+ }
55548
+ snapshot = await inspectCodebase(
55277
55549
  codebase.folder,
55278
- codebase.defaultBranch,
55279
55550
  INSPECTION_TIMEOUT_MS,
55280
- signal2
55551
+ signal2,
55552
+ codebase.canonicalOrigin
55281
55553
  );
55554
+ } else {
55555
+ fetchError = (fetchResult.stderr || "Git fetch failed").replace(/([a-z][a-z0-9+.-]*:\/\/)[^\s/@]+@/gi, "$1").slice(0, 2e3);
55282
55556
  }
55283
- snapshot = await inspectCodebase(
55557
+ }
55558
+ let inventory;
55559
+ try {
55560
+ inventory = await discoverWorktrees(
55284
55561
  codebase.folder,
55562
+ new Map(
55563
+ codebase.worktrees.map((worktree) => [
55564
+ worktree.gitDirectory,
55565
+ worktree.baseBranchOverride
55566
+ ])
55567
+ ),
55568
+ codebase.defaultBranch,
55285
55569
  INSPECTION_TIMEOUT_MS,
55286
- signal2,
55287
- codebase.canonicalOrigin
55570
+ signal2
55288
55571
  );
55289
- } else {
55290
- fetchError = (fetchResult.stderr || "Git fetch failed").replace(/([a-z][a-z0-9+.-]*:\/\/)[^\s/@]+@/gi, "$1").slice(0, 2e3);
55572
+ } catch (error) {
55573
+ inventory = {
55574
+ complete: false,
55575
+ defaultBranch: null,
55576
+ localBranches: [],
55577
+ remoteBranches: [],
55578
+ worktrees: []
55579
+ };
55580
+ fetchError ??= error instanceof Error ? error.message.slice(0, 2e3) : String(error);
55291
55581
  }
55292
- }
55293
- let inventory;
55294
- try {
55295
- inventory = await discoverWorktrees(
55296
- codebase.folder,
55297
- new Map(
55298
- codebase.worktrees.map((worktree) => [
55299
- worktree.gitDirectory,
55300
- worktree.baseBranchOverride
55301
- ])
55302
- ),
55303
- codebase.defaultBranch,
55304
- INSPECTION_TIMEOUT_MS,
55305
- signal2
55306
- );
55307
- } catch (error) {
55308
- inventory = {
55309
- complete: false,
55310
- defaultBranch: null,
55311
- localBranches: [],
55312
- remoteBranches: [],
55313
- worktrees: []
55314
- };
55315
- fetchError ??= error instanceof Error ? error.message.slice(0, 2e3) : String(error);
55316
- }
55317
- const localBranchInventoryAttemptedAt = (/* @__PURE__ */ new Date()).toISOString();
55318
- let localBranchInventory = null;
55319
- let localBranchInventoryError = null;
55320
- try {
55321
- if (snapshot.availability !== "AVAILABLE")
55322
- throw new Error(snapshot.error || "Codebase is unavailable");
55323
- localBranchInventory = await scanLocalBranches(
55324
- codebase.folder,
55325
- INSPECTION_TIMEOUT_MS,
55326
- signal2
55327
- );
55328
- } catch (error) {
55329
- localBranchInventoryError = error instanceof Error ? error.message.slice(0, 2e3) : String(error).slice(0, 2e3);
55330
- }
55331
- if (signal2.aborted) return;
55332
- await this.client.reportCodebaseStatuses([
55333
- {
55334
- codebaseId: codebase.id,
55335
- snapshot,
55336
- localBranchInventory,
55337
- localBranchInventoryError,
55338
- localBranchInventoryAttemptedAt
55339
- }
55340
- ]);
55341
- await this.client.reportWorktrees([
55342
- {
55343
- codebaseId: codebase.id,
55344
- ...inventory,
55345
- fetchedAt: snapshot.fetchedAt,
55346
- fetchAttemptedAt,
55347
- fetchError
55582
+ const localBranchInventoryAttemptedAt = (/* @__PURE__ */ new Date()).toISOString();
55583
+ let localBranchInventory = null;
55584
+ let localBranchInventoryError = null;
55585
+ try {
55586
+ if (snapshot.availability !== "AVAILABLE")
55587
+ throw new Error(
55588
+ snapshot.error || "Codebase is unavailable"
55589
+ );
55590
+ localBranchInventory = await scanLocalBranches(
55591
+ codebase.folder,
55592
+ INSPECTION_TIMEOUT_MS,
55593
+ signal2
55594
+ );
55595
+ } catch (error) {
55596
+ localBranchInventoryError = error instanceof Error ? error.message.slice(0, 2e3) : String(error).slice(0, 2e3);
55348
55597
  }
55349
- ]);
55350
- })
55598
+ if (signal2.aborted) return;
55599
+ await this.client.reportCodebaseStatuses([
55600
+ {
55601
+ codebaseId: codebase.id,
55602
+ snapshot,
55603
+ localBranchInventory,
55604
+ localBranchInventoryError,
55605
+ localBranchInventoryAttemptedAt
55606
+ }
55607
+ ]);
55608
+ await this.client.reportWorktrees([
55609
+ {
55610
+ codebaseId: codebase.id,
55611
+ ...inventory,
55612
+ fetchedAt: snapshot.fetchedAt,
55613
+ fetchAttemptedAt,
55614
+ fetchError
55615
+ }
55616
+ ]);
55617
+ }
55618
+ )
55351
55619
  )
55352
55620
  );
55353
55621
  }
@@ -56200,7 +56468,9 @@ var OpenCodeAdapter = class {
56200
56468
  initial = new Set(
56201
56469
  (await openCodeMessages(client, nativeId)).map((entry) => entry.id)
56202
56470
  );
56203
- await callbacks.onNativeId(nativeId, runtime.version);
56471
+ await callbacks.onNativeId(nativeId, runtime.version, {
56472
+ workingDirectory: cwd
56473
+ });
56204
56474
  } catch (error) {
56205
56475
  await cleanup();
56206
56476
  throw error;
@@ -56645,7 +56915,8 @@ var OpenCodeAdapter = class {
56645
56915
  pricingSource: "opencode-history",
56646
56916
  createdAt: opencodeTimestamp(session.time.created),
56647
56917
  updatedAt: opencodeTimestamp(session.time.updated),
56648
- rawMetadata: session
56918
+ rawMetadata: session,
56919
+ sessionInfo: { workingDirectory: session.location.directory }
56649
56920
  });
56650
56921
  }
56651
56922
  }
@@ -56945,12 +57216,17 @@ var RunManager = class {
56945
57216
  return;
56946
57217
  }
56947
57218
  const callbacks = {
56948
- onNativeId: async (nativeId, providerVersion) => {
57219
+ onNativeId: async (nativeId, providerVersion, sessionInfo) => {
56949
57220
  await this.retry(
56950
57221
  () => this.client.updateRunAttemptNativeId(
56951
57222
  attempt.id,
56952
57223
  nativeId,
56953
- providerVersion
57224
+ providerVersion,
57225
+ {
57226
+ workingDirectory: run.worktree.folder,
57227
+ ...sessionInfo,
57228
+ activityLogPath: journal.eventsPath
57229
+ }
56954
57230
  )
56955
57231
  );
56956
57232
  },
@@ -57091,7 +57367,8 @@ var RunManager = class {
57091
57367
  await this.retryFinalization(
57092
57368
  () => this.client.reportRunCheckpoint(run.id, attemptId, {
57093
57369
  ...finalCheckpoint,
57094
- diffPatch: comparison.rollbackPatch
57370
+ diffPatch: comparison.worktreePatch || null,
57371
+ diffSummary: comparison.worktreeSummary || null
57095
57372
  })
57096
57373
  );
57097
57374
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-development-environment/control-agent",
3
- "version": "0.0.137",
3
+ "version": "0.0.139",
4
4
  "description": "Control agent for the AI Development Environment",
5
5
  "license": "MIT",
6
6
  "repository": {