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

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 +382 -186
  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) {
@@ -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.138",
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,129 @@ 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
+ /** Only read-only worktree inspection jobs may overlap reconciliation. */
45344
+ runInspection(key, task, signal2) {
45345
+ return this.enqueue(key, "inspection", () => task(), signal2);
45346
+ }
45347
+ /** Keep reconciliation and its reports ordered with all user mutations. */
45348
+ runReconciliation(key, task, signal2) {
45349
+ return this.enqueue(
45350
+ key,
45351
+ "reconciliation",
45352
+ (queue) => task({
45353
+ runExclusive: (exclusiveTask) => this.runReconciliationExclusive(key, queue, exclusiveTask)
45354
+ }),
45355
+ signal2
45356
+ );
45357
+ }
45358
+ runReconciliationExclusive(key, queue, task) {
45359
+ if (!queue.reconciliation || queue.exclusiveReconciliationPhase) {
45360
+ return Promise.reject(new Error("Invalid reconciliation phase"));
45361
+ }
45362
+ return new Promise((resolve11, reject) => {
45363
+ const complete = () => {
45364
+ queue.exclusiveReconciliationPhase = null;
45365
+ queue.exclusive = false;
45366
+ this.drain(key, queue);
45367
+ };
45368
+ queue.exclusiveReconciliationPhase = () => {
45369
+ void Promise.resolve().then(task).then(
45370
+ (result) => {
45371
+ complete();
45372
+ resolve11(result);
45373
+ },
45374
+ (error) => {
45375
+ complete();
45376
+ reject(error);
45377
+ }
45378
+ );
45379
+ };
45380
+ this.drain(key, queue);
45307
45381
  });
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);
45382
+ }
45383
+ enqueue(key, kind, task, signal2) {
45384
+ if (signal2?.aborted) return Promise.reject(signal2.reason);
45385
+ let queue = this.queues.get(key);
45386
+ if (!queue) {
45387
+ queue = {
45388
+ inspections: 0,
45389
+ reconciliation: false,
45390
+ exclusive: false,
45391
+ exclusiveReconciliationPhase: null,
45392
+ pending: []
45393
+ };
45394
+ this.queues.set(key, queue);
45395
+ }
45396
+ const repositoryQueue = queue;
45397
+ return new Promise((resolve11, reject) => {
45398
+ let pending = true;
45399
+ const complete = () => {
45400
+ if (kind === "inspection") repositoryQueue.inspections -= 1;
45401
+ else if (kind === "reconciliation")
45402
+ repositoryQueue.reconciliation = false;
45403
+ else repositoryQueue.exclusive = false;
45404
+ this.drain(key, repositoryQueue);
45405
+ };
45406
+ const abort = () => {
45407
+ if (!pending) return;
45408
+ pending = false;
45409
+ const index = repositoryQueue.pending.indexOf(operation);
45410
+ if (index !== -1) repositoryQueue.pending.splice(index, 1);
45411
+ signal2?.removeEventListener("abort", abort);
45412
+ reject(signal2?.reason);
45413
+ this.drain(key, repositoryQueue);
45414
+ };
45415
+ const operation = {
45416
+ kind,
45417
+ start: () => {
45418
+ pending = false;
45419
+ signal2?.removeEventListener("abort", abort);
45420
+ void Promise.resolve().then(() => {
45421
+ signal2?.throwIfAborted();
45422
+ return task(repositoryQueue);
45423
+ }).then(
45424
+ (result) => {
45425
+ complete();
45426
+ resolve11(result);
45427
+ },
45428
+ (error) => {
45429
+ complete();
45430
+ reject(error);
45431
+ }
45432
+ );
45433
+ }
45434
+ };
45435
+ repositoryQueue.pending.push(operation);
45436
+ signal2?.addEventListener("abort", abort, { once: true });
45437
+ if (signal2?.aborted) abort();
45438
+ else this.drain(key, repositoryQueue);
45439
+ });
45440
+ }
45441
+ drain(key, queue) {
45442
+ if (queue.exclusiveReconciliationPhase) {
45443
+ if (!queue.exclusive && queue.inspections === 0) {
45444
+ queue.exclusive = true;
45445
+ queue.exclusiveReconciliationPhase();
45446
+ }
45447
+ return;
45448
+ }
45449
+ while (!queue.exclusive && queue.pending.length) {
45450
+ const next = queue.pending[0];
45451
+ if (next.kind === "exclusive" && (queue.inspections > 0 || queue.reconciliation) || next.kind === "reconciliation" && queue.reconciliation) {
45452
+ return;
45453
+ }
45454
+ queue.pending.shift();
45455
+ if (next.kind === "inspection") queue.inspections += 1;
45456
+ else if (next.kind === "reconciliation") queue.reconciliation = true;
45457
+ else queue.exclusive = true;
45458
+ next.start();
45459
+ }
45460
+ if (!queue.exclusive && !queue.reconciliation && queue.inspections === 0 && queue.pending.length === 0) {
45461
+ this.queues.delete(key);
45316
45462
  }
45317
45463
  }
45318
45464
  };
@@ -48322,7 +48468,9 @@ var downloadIosBuildArtifact = async (payload, timeoutMs, signal2, onLog, contex
48322
48468
  uploadId: input.uploadId,
48323
48469
  path: uploadPath,
48324
48470
  filename,
48325
- contentType
48471
+ contentType,
48472
+ signal: signal2,
48473
+ timeoutMs
48326
48474
  });
48327
48475
  return successfulProcess3;
48328
48476
  } finally {
@@ -51028,6 +51176,17 @@ async function compareGitCheckpoint(cwd, target2, current) {
51028
51176
  ],
51029
51177
  PATCH_LIMIT
51030
51178
  );
51179
+ const worktreeSummary = await cappedGit(
51180
+ cwd,
51181
+ [
51182
+ "diff",
51183
+ "--stat",
51184
+ "--find-renames",
51185
+ target2.worktreeTree,
51186
+ current.worktreeTree
51187
+ ],
51188
+ SUMMARY_LIMIT
51189
+ );
51031
51190
  const indexPatch = target2.indexTree && current.indexTree ? await cappedGit(
51032
51191
  cwd,
51033
51192
  [
@@ -51048,6 +51207,8 @@ async function compareGitCheckpoint(cwd, target2, current) {
51048
51207
  ])
51049
51208
  ) : 0;
51050
51209
  return {
51210
+ worktreePatch,
51211
+ worktreeSummary,
51051
51212
  rollbackPatch: [
51052
51213
  worktreePatch && `Worktree changes that will be undone:
51053
51214
 
@@ -52900,7 +53061,9 @@ var downloadWorktreeDiffAsset = async (payload, timeoutMs, signal2, _onLog, cont
52900
53061
  uploadId: input.uploadId,
52901
53062
  path: uploadPath,
52902
53063
  filename: basename5(input.path),
52903
- contentType: imageContentType(input.path)
53064
+ contentType: imageContentType(input.path),
53065
+ signal: signal2,
53066
+ timeoutMs
52904
53067
  });
52905
53068
  return successfulProcess9;
52906
53069
  } finally {
@@ -55146,39 +55309,55 @@ var JobExecutor = class {
55146
55309
  const handler = handlers[claimed.kind];
55147
55310
  if (!handler)
55148
55311
  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
- );
55312
+ const runHandler = () => {
55313
+ controller.signal.throwIfAborted();
55314
+ return handler(
55315
+ claimed.payload,
55316
+ claimed.timeoutSeconds * 1e3,
55317
+ controller.signal,
55318
+ (log2) => this.client.appendLog(claimed.id, log2).then(() => void 0),
55319
+ {
55320
+ agentId: claimed.agentId,
55321
+ reportBranchDeletionResult: (result2) => this.client.reportBranchDeletionResult(claimed.id, result2),
55322
+ reportWorktreeActivity: (input) => this.client.reportWorktreeActivity(input),
55323
+ refreshFetchedCodebase: (input) => refreshFetchedCodebase(
55324
+ this.client,
55325
+ input,
55326
+ claimed.timeoutSeconds * 1e3,
55327
+ controller.signal
55328
+ ),
55329
+ reportBuildProgress: (input) => this.client.reportBuildProgress(input),
55330
+ reportBuildDeploymentLaunched: (deploymentId, launchedAt) => this.client.reportBuildDeploymentLaunched(
55331
+ claimed.id,
55332
+ deploymentId,
55333
+ launchedAt
55334
+ ),
55335
+ appendBuildLogChunks: (buildId, chunks) => this.client.appendBuildLogChunks(buildId, chunks),
55336
+ uploadBuildArtifact: (input) => this.client.uploadBuildArtifact(input),
55337
+ uploadBuildArtifactTransfer: (input) => this.client.uploadBuildArtifactTransfer(input),
55338
+ downloadBuildArtifactTransfer: (input) => this.client.downloadBuildArtifactTransfer(input),
55339
+ downloadDsymDwarf: (input) => this.client.downloadDsymDwarf(input),
55340
+ claimSigningSecretTransfer: (transferId) => this.client.claimSigningSecretTransfer(transferId),
55341
+ claimWorkflowJobSecrets: () => this.client.claimWorkflowJobSecrets(claimed.id),
55342
+ appendCommandOutput: (attemptId, chunks) => this.client.appendCommandOutput(claimed.id, attemptId, chunks)
55343
+ }
55344
+ );
55345
+ };
55180
55346
  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();
55347
+ if (!codebaseId) result = await runHandler();
55348
+ 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) {
55349
+ result = await this.repositoryCoordinator.runInspection(
55350
+ codebaseId,
55351
+ runHandler,
55352
+ controller.signal
55353
+ );
55354
+ } else {
55355
+ result = await this.repositoryCoordinator.run(
55356
+ codebaseId,
55357
+ runHandler,
55358
+ controller.signal
55359
+ );
55360
+ }
55182
55361
  status2 = result.cancelled ? "CANCELLED" : result.timedOut ? "TIMED_OUT" : result.exitCode === 0 ? "SUCCEEDED" : "FAILED";
55183
55362
  completionError = status2 === "FAILED" ? `Process exited with code ${result.exitCode}` : void 0;
55184
55363
  } catch (error) {
@@ -55244,110 +55423,118 @@ var CodebaseMonitor = class {
55244
55423
  const batch = codebases.slice(index, index + CONCURRENCY2);
55245
55424
  await Promise.all(
55246
55425
  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(
55426
+ (codebase) => this.repositoryCoordinator.runReconciliation(
55427
+ codebase.id,
55428
+ async ({ runExclusive }) => {
55429
+ let snapshot = await inspectCodebase(
55430
+ codebase.folder,
55431
+ INSPECTION_TIMEOUT_MS,
55432
+ signal2,
55433
+ codebase.canonicalOrigin
55434
+ );
55435
+ let fetchAttemptedAt = null;
55436
+ let fetchError = null;
55437
+ const fetchedAt2 = Math.max(
55438
+ snapshot.fetchedAt ? new Date(snapshot.fetchedAt).getTime() : 0,
55439
+ codebase.lastFetchAttemptAt ? new Date(codebase.lastFetchAttemptAt).getTime() : 0
55440
+ );
55441
+ const fetchDue = snapshot.availability === "AVAILABLE" && Date.now() - fetchedAt2 >= configuration.fetchIntervalSeconds * 1e3;
55442
+ if (fetchDue && !signal2.aborted) {
55443
+ fetchAttemptedAt = (/* @__PURE__ */ new Date()).toISOString();
55444
+ const fetchResult = await captureCommand({
55445
+ command: "git",
55446
+ args: ["-C", codebase.folder, "fetch", "origin"],
55447
+ timeoutMs: 3e5,
55448
+ signal: signal2,
55449
+ env: {
55450
+ ...process.env,
55451
+ GIT_TERMINAL_PROMPT: "0",
55452
+ GIT_OPTIONAL_LOCKS: "0"
55453
+ }
55454
+ });
55455
+ if (fetchResult.exitCode === 0) {
55456
+ if (codebase.keepBaseBranchUpToDate && codebase.defaultBranch) {
55457
+ const baseBranch = codebase.defaultBranch;
55458
+ await runExclusive(
55459
+ () => updateBaseBranchAfterFetch(
55460
+ codebase.folder,
55461
+ baseBranch,
55462
+ INSPECTION_TIMEOUT_MS,
55463
+ signal2
55464
+ )
55465
+ );
55466
+ }
55467
+ snapshot = await inspectCodebase(
55277
55468
  codebase.folder,
55278
- codebase.defaultBranch,
55279
55469
  INSPECTION_TIMEOUT_MS,
55280
- signal2
55470
+ signal2,
55471
+ codebase.canonicalOrigin
55281
55472
  );
55473
+ } else {
55474
+ fetchError = (fetchResult.stderr || "Git fetch failed").replace(/([a-z][a-z0-9+.-]*:\/\/)[^\s/@]+@/gi, "$1").slice(0, 2e3);
55282
55475
  }
55283
- snapshot = await inspectCodebase(
55476
+ }
55477
+ let inventory;
55478
+ try {
55479
+ inventory = await discoverWorktrees(
55284
55480
  codebase.folder,
55481
+ new Map(
55482
+ codebase.worktrees.map((worktree) => [
55483
+ worktree.gitDirectory,
55484
+ worktree.baseBranchOverride
55485
+ ])
55486
+ ),
55487
+ codebase.defaultBranch,
55285
55488
  INSPECTION_TIMEOUT_MS,
55286
- signal2,
55287
- codebase.canonicalOrigin
55489
+ signal2
55288
55490
  );
55289
- } else {
55290
- fetchError = (fetchResult.stderr || "Git fetch failed").replace(/([a-z][a-z0-9+.-]*:\/\/)[^\s/@]+@/gi, "$1").slice(0, 2e3);
55291
- }
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
55491
+ } catch (error) {
55492
+ inventory = {
55493
+ complete: false,
55494
+ defaultBranch: null,
55495
+ localBranches: [],
55496
+ remoteBranches: [],
55497
+ worktrees: []
55498
+ };
55499
+ fetchError ??= error instanceof Error ? error.message.slice(0, 2e3) : String(error);
55339
55500
  }
55340
- ]);
55341
- await this.client.reportWorktrees([
55342
- {
55343
- codebaseId: codebase.id,
55344
- ...inventory,
55345
- fetchedAt: snapshot.fetchedAt,
55346
- fetchAttemptedAt,
55347
- fetchError
55501
+ const localBranchInventoryAttemptedAt = (/* @__PURE__ */ new Date()).toISOString();
55502
+ let localBranchInventory = null;
55503
+ let localBranchInventoryError = null;
55504
+ try {
55505
+ if (snapshot.availability !== "AVAILABLE")
55506
+ throw new Error(
55507
+ snapshot.error || "Codebase is unavailable"
55508
+ );
55509
+ localBranchInventory = await scanLocalBranches(
55510
+ codebase.folder,
55511
+ INSPECTION_TIMEOUT_MS,
55512
+ signal2
55513
+ );
55514
+ } catch (error) {
55515
+ localBranchInventoryError = error instanceof Error ? error.message.slice(0, 2e3) : String(error).slice(0, 2e3);
55348
55516
  }
55349
- ]);
55350
- })
55517
+ if (signal2.aborted) return;
55518
+ await this.client.reportCodebaseStatuses([
55519
+ {
55520
+ codebaseId: codebase.id,
55521
+ snapshot,
55522
+ localBranchInventory,
55523
+ localBranchInventoryError,
55524
+ localBranchInventoryAttemptedAt
55525
+ }
55526
+ ]);
55527
+ await this.client.reportWorktrees([
55528
+ {
55529
+ codebaseId: codebase.id,
55530
+ ...inventory,
55531
+ fetchedAt: snapshot.fetchedAt,
55532
+ fetchAttemptedAt,
55533
+ fetchError
55534
+ }
55535
+ ]);
55536
+ }
55537
+ )
55351
55538
  )
55352
55539
  );
55353
55540
  }
@@ -56200,7 +56387,9 @@ var OpenCodeAdapter = class {
56200
56387
  initial = new Set(
56201
56388
  (await openCodeMessages(client, nativeId)).map((entry) => entry.id)
56202
56389
  );
56203
- await callbacks.onNativeId(nativeId, runtime.version);
56390
+ await callbacks.onNativeId(nativeId, runtime.version, {
56391
+ workingDirectory: cwd
56392
+ });
56204
56393
  } catch (error) {
56205
56394
  await cleanup();
56206
56395
  throw error;
@@ -56645,7 +56834,8 @@ var OpenCodeAdapter = class {
56645
56834
  pricingSource: "opencode-history",
56646
56835
  createdAt: opencodeTimestamp(session.time.created),
56647
56836
  updatedAt: opencodeTimestamp(session.time.updated),
56648
- rawMetadata: session
56837
+ rawMetadata: session,
56838
+ sessionInfo: { workingDirectory: session.location.directory }
56649
56839
  });
56650
56840
  }
56651
56841
  }
@@ -56945,12 +57135,17 @@ var RunManager = class {
56945
57135
  return;
56946
57136
  }
56947
57137
  const callbacks = {
56948
- onNativeId: async (nativeId, providerVersion) => {
57138
+ onNativeId: async (nativeId, providerVersion, sessionInfo) => {
56949
57139
  await this.retry(
56950
57140
  () => this.client.updateRunAttemptNativeId(
56951
57141
  attempt.id,
56952
57142
  nativeId,
56953
- providerVersion
57143
+ providerVersion,
57144
+ {
57145
+ workingDirectory: run.worktree.folder,
57146
+ ...sessionInfo,
57147
+ activityLogPath: journal.eventsPath
57148
+ }
56954
57149
  )
56955
57150
  );
56956
57151
  },
@@ -57091,7 +57286,8 @@ var RunManager = class {
57091
57286
  await this.retryFinalization(
57092
57287
  () => this.client.reportRunCheckpoint(run.id, attemptId, {
57093
57288
  ...finalCheckpoint,
57094
- diffPatch: comparison.rollbackPatch
57289
+ diffPatch: comparison.worktreePatch || null,
57290
+ diffSummary: comparison.worktreeSummary || null
57095
57291
  })
57096
57292
  );
57097
57293
  }
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.138",
4
4
  "description": "Control agent for the AI Development Environment",
5
5
  "license": "MIT",
6
6
  "repository": {