@ai-development-environment/control-agent 0.0.88 → 0.0.89

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 +267 -30
  2. package/package.json +1 -1
@@ -8321,9 +8321,64 @@ function buildCoverageReportPayload(input) {
8321
8321
  };
8322
8322
  }
8323
8323
 
8324
+ // ../agent-contract/src/cli-health.ts
8325
+ var CLI_HEALTH_JOB_KIND = "cli.health";
8326
+ var CLI_HEALTH_MAX_CHECKS = 27;
8327
+ var CLI_HEALTH_MAX_OUTPUT_BYTES = 32 * 1024;
8328
+ var CLI_HEALTH_CHECK_TIMEOUT_MS = 15e3;
8329
+ function objectValue12(value, name) {
8330
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
8331
+ throw new Error(`${name} must be an object`);
8332
+ }
8333
+ return value;
8334
+ }
8335
+ function checkDefinition(value, index) {
8336
+ const name = `cli.health checks[${index}]`;
8337
+ const check = objectValue12(value, name);
8338
+ const allowed = /* @__PURE__ */ new Set(["id", "name", "command", "builtIn"]);
8339
+ const unexpected = Object.keys(check).find((key) => !allowed.has(key));
8340
+ if (unexpected) throw new Error(`Unexpected ${name} field: ${unexpected}`);
8341
+ for (const field of ["id", "name", "command"]) {
8342
+ if (typeof check[field] !== "string" || check[field].length === 0) {
8343
+ throw new Error(`${name}.${field} must be a non-empty string`);
8344
+ }
8345
+ }
8346
+ if (typeof check.builtIn !== "boolean") {
8347
+ throw new Error(`${name}.builtIn must be a boolean`);
8348
+ }
8349
+ if (check.id.length > 100)
8350
+ throw new Error(`${name}.id is too long`);
8351
+ if (check.name.length > 100)
8352
+ throw new Error(`${name}.name is too long`);
8353
+ if (check.command.length > 4096 || check.command.includes("\0")) {
8354
+ throw new Error(`${name}.command is invalid`);
8355
+ }
8356
+ return check;
8357
+ }
8358
+ function parseCliHealthJobPayload(value) {
8359
+ const payload = objectValue12(value, "cli.health payload");
8360
+ const unexpected = Object.keys(payload).find((key) => key !== "checks");
8361
+ if (unexpected)
8362
+ throw new Error(`Unexpected cli.health payload field: ${unexpected}`);
8363
+ if (!Array.isArray(payload.checks)) {
8364
+ throw new Error("cli.health payload.checks must be an array");
8365
+ }
8366
+ if (payload.checks.length > CLI_HEALTH_MAX_CHECKS) {
8367
+ throw new Error(
8368
+ `cli.health supports at most ${CLI_HEALTH_MAX_CHECKS} checks`
8369
+ );
8370
+ }
8371
+ const checks = payload.checks.map(checkDefinition);
8372
+ if (new Set(checks.map((check) => check.id)).size !== checks.length) {
8373
+ throw new Error("cli.health check identifiers must be unique");
8374
+ }
8375
+ return { checks };
8376
+ }
8377
+
8324
8378
  // src/inventory.ts
8325
8379
  var AGENT_VERSION = process.env.CONTROL_AGENT_VERSION ?? "0.1.0";
8326
8380
  var PORTABLE_AGENT_CAPABILITIES = [
8381
+ CLI_HEALTH_JOB_KIND,
8327
8382
  COMMAND_RUN_JOB_KIND,
8328
8383
  CCUSAGE_REPORT_JOB_KIND,
8329
8384
  ...CODEBASE_JOB_KINDS,
@@ -8390,12 +8445,17 @@ function captureCommand(options) {
8390
8445
  timedOut: !options.signal.aborted,
8391
8446
  cancelled: options.signal.aborted,
8392
8447
  stdout: "",
8393
- stderr: ""
8448
+ stderr: "",
8449
+ outputTruncated: false
8394
8450
  });
8395
8451
  }
8396
8452
  return new Promise((resolve8, reject) => {
8397
- let stdout = "";
8398
- let stderr = "";
8453
+ const stdoutChunks = [];
8454
+ const stderrChunks = [];
8455
+ let stdoutBytes = 0;
8456
+ let stderrBytes = 0;
8457
+ let retainedBytes = 0;
8458
+ let outputTruncated = false;
8399
8459
  let timedOut = false;
8400
8460
  let cancelled = false;
8401
8461
  let settled = false;
@@ -8415,12 +8475,23 @@ function captureCommand(options) {
8415
8475
  // stop the entire command tree instead of only the immediate Git process.
8416
8476
  detached
8417
8477
  });
8418
- const append = (current, chunk) => `${current}${String(chunk)}`.slice(0, MAX_OUTPUT_BYTES);
8478
+ const append = (chunks, chunk, streamBytes) => {
8479
+ const bytes = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
8480
+ const remaining = Math.max(
8481
+ 0,
8482
+ options.maxOutputBytes === void 0 ? MAX_OUTPUT_BYTES - streamBytes : options.maxOutputBytes - retainedBytes
8483
+ );
8484
+ const retained = bytes.subarray(0, remaining);
8485
+ if (retained.length) chunks.push(Buffer.from(retained));
8486
+ retainedBytes += retained.length;
8487
+ outputTruncated ||= retained.length < bytes.length;
8488
+ return streamBytes + retained.length;
8489
+ };
8419
8490
  child.stdout?.on("data", (chunk) => {
8420
- stdout = append(stdout, chunk);
8491
+ stdoutBytes = append(stdoutChunks, chunk, stdoutBytes);
8421
8492
  });
8422
8493
  child.stderr?.on("data", (chunk) => {
8423
- stderr = append(stderr, chunk);
8494
+ stderrBytes = append(stderrChunks, chunk, stderrBytes);
8424
8495
  });
8425
8496
  const sendSignal = (signal) => {
8426
8497
  if (detached && child.pid) {
@@ -8428,12 +8499,18 @@ function captureCommand(options) {
8428
8499
  process.kill(-child.pid, signal);
8429
8500
  return;
8430
8501
  } catch (error) {
8431
- if (!(error instanceof Error) || !("code" in error) || error.code !== "ESRCH") {
8502
+ if (!(error instanceof Error) || !("code" in error) || error.code !== "ESRCH" && error.code !== "EPERM") {
8432
8503
  throw error;
8433
8504
  }
8434
8505
  }
8435
8506
  }
8436
- child.kill(signal);
8507
+ try {
8508
+ child.kill(signal);
8509
+ } catch (error) {
8510
+ if (!(error instanceof Error) || !("code" in error) || error.code !== "ESRCH" && error.code !== "EPERM") {
8511
+ throw error;
8512
+ }
8513
+ }
8437
8514
  };
8438
8515
  const terminate = () => {
8439
8516
  if (child.exitCode !== null || child.killed) return;
@@ -8473,8 +8550,9 @@ function captureCommand(options) {
8473
8550
  signal,
8474
8551
  timedOut,
8475
8552
  cancelled,
8476
- stdout,
8477
- stderr
8553
+ stdout: Buffer.concat(stdoutChunks, stdoutBytes).toString("utf8"),
8554
+ stderr: Buffer.concat(stderrChunks, stderrBytes).toString("utf8"),
8555
+ outputTruncated
8478
8556
  });
8479
8557
  });
8480
8558
  });
@@ -11760,6 +11838,164 @@ var importCoverageReport = async (payloadValue, timeoutMs, signal, onLog) => {
11760
11838
  };
11761
11839
  };
11762
11840
 
11841
+ // src/handlers/cli-health.ts
11842
+ import { Buffer as Buffer2 } from "node:buffer";
11843
+ import { homedir as homedir3 } from "node:os";
11844
+ var CONCURRENCY = 4;
11845
+ var ANSI_PATTERN = /\u001B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])/g;
11846
+ var CONTROL_PATTERN = /[\u0000-\u0008\u000B\u000C\u000E-\u001A\u001C-\u001F\u007F]/g;
11847
+ function clean(value) {
11848
+ return value.replace(ANSI_PATTERN, "").replace(CONTROL_PATTERN, "");
11849
+ }
11850
+ function lineBufferedOutput(value) {
11851
+ return value.replace(/\r?\n$/, "");
11852
+ }
11853
+ function appendWithinLimit(current, line, retainedBytes) {
11854
+ const addition = current ? `
11855
+ ${line}` : line;
11856
+ if (!addition) return { value: current, retainedBytes, truncated: false };
11857
+ const remaining = CLI_HEALTH_MAX_OUTPUT_BYTES - retainedBytes;
11858
+ if (remaining <= 0) return { value: current, retainedBytes, truncated: true };
11859
+ const bytes = Buffer2.from(addition, "utf8");
11860
+ if (bytes.length <= remaining)
11861
+ return {
11862
+ value: current + addition,
11863
+ retainedBytes: retainedBytes + bytes.length,
11864
+ truncated: false
11865
+ };
11866
+ let end = remaining;
11867
+ let retained = "";
11868
+ while (end > 0) {
11869
+ try {
11870
+ retained = new TextDecoder("utf-8", { fatal: true }).decode(
11871
+ bytes.subarray(0, end)
11872
+ );
11873
+ break;
11874
+ } catch {
11875
+ end -= 1;
11876
+ }
11877
+ }
11878
+ return {
11879
+ value: current + retained,
11880
+ retainedBytes: retainedBytes + Buffer2.byteLength(retained, "utf8"),
11881
+ truncated: true
11882
+ };
11883
+ }
11884
+ async function reportOutput(check, stdout, stderr, createdAt, onLog) {
11885
+ for (const [stream, message] of [
11886
+ ["STDOUT", stdout],
11887
+ ["STDERR", stderr]
11888
+ ]) {
11889
+ if (!message) continue;
11890
+ try {
11891
+ await onLog({
11892
+ sequence: 0,
11893
+ stream,
11894
+ message: `[${check.name}] ${message}`,
11895
+ createdAt
11896
+ });
11897
+ } catch (error) {
11898
+ console.error(
11899
+ `Could not append CLI health log for ${check.id}:`,
11900
+ error instanceof Error ? error.message : error
11901
+ );
11902
+ }
11903
+ }
11904
+ }
11905
+ async function runCliHealthCheck(check, signal, onLog, options = {}) {
11906
+ const started = Date.now();
11907
+ const checkedAt = (/* @__PURE__ */ new Date()).toISOString();
11908
+ try {
11909
+ const shell = options.shell || process.env.SHELL || "/bin/sh";
11910
+ const timeoutMs = options.timeoutMs ?? CLI_HEALTH_CHECK_TIMEOUT_MS;
11911
+ const result = await captureCommand({
11912
+ command: shell,
11913
+ args: ["-lc", check.command],
11914
+ cwd: options.cwd || homedir3(),
11915
+ timeoutMs,
11916
+ signal,
11917
+ maxOutputBytes: CLI_HEALTH_MAX_OUTPUT_BYTES
11918
+ });
11919
+ const capturedStdout = appendWithinLimit(
11920
+ "",
11921
+ clean(lineBufferedOutput(result.stdout)),
11922
+ 0
11923
+ );
11924
+ const systemError = result.timedOut ? `Process exceeded its ${Math.round(timeoutMs / 1e3)} second timeout` : result.cancelled ? "Cancellation requested" : "";
11925
+ const capturedStderr = appendWithinLimit(
11926
+ "",
11927
+ [clean(lineBufferedOutput(result.stderr)), systemError].filter(Boolean).join("\n"),
11928
+ capturedStdout.retainedBytes
11929
+ );
11930
+ const stdout = capturedStdout.value;
11931
+ const stderr = capturedStderr.value;
11932
+ const outputTruncated = result.outputTruncated || capturedStdout.truncated || capturedStderr.truncated;
11933
+ await reportOutput(check, stdout, stderr, checkedAt, onLog);
11934
+ return {
11935
+ ...check,
11936
+ exitCode: result.exitCode,
11937
+ stdout,
11938
+ stderr,
11939
+ durationMs: Date.now() - started,
11940
+ checkedAt,
11941
+ timedOut: result.timedOut,
11942
+ launchError: null,
11943
+ outputTruncated
11944
+ };
11945
+ } catch (error) {
11946
+ const launchError = clean(
11947
+ error instanceof Error ? error.message : String(error)
11948
+ );
11949
+ await reportOutput(check, "", launchError, checkedAt, onLog);
11950
+ return {
11951
+ ...check,
11952
+ exitCode: null,
11953
+ stdout: "",
11954
+ stderr: launchError,
11955
+ durationMs: Date.now() - started,
11956
+ checkedAt,
11957
+ timedOut: false,
11958
+ launchError,
11959
+ outputTruncated: false
11960
+ };
11961
+ }
11962
+ }
11963
+ async function runCliHealth(payload, _timeoutMs, signal, onLog) {
11964
+ const { checks } = parseCliHealthJobPayload(payload);
11965
+ const results = new Array(checks.length);
11966
+ let next = 0;
11967
+ let logSequence = 0;
11968
+ let logChain = Promise.resolve();
11969
+ const appendSweepLog = (log) => {
11970
+ const sequenced = { ...log, sequence: logSequence++ };
11971
+ const task = logChain.then(() => onLog(sequenced));
11972
+ logChain = task.catch(() => void 0);
11973
+ return task;
11974
+ };
11975
+ const worker = async () => {
11976
+ while (!signal.aborted) {
11977
+ const index = next++;
11978
+ if (index >= checks.length) return;
11979
+ results[index] = await runCliHealthCheck(
11980
+ checks[index],
11981
+ signal,
11982
+ appendSweepLog
11983
+ );
11984
+ }
11985
+ };
11986
+ await Promise.all(
11987
+ Array.from({ length: Math.min(CONCURRENCY, checks.length) }, worker)
11988
+ );
11989
+ await logChain;
11990
+ return {
11991
+ exitCode: signal.aborted ? null : 0,
11992
+ signal: null,
11993
+ timedOut: false,
11994
+ cancelled: signal.aborted,
11995
+ checks: results.filter(Boolean)
11996
+ };
11997
+ }
11998
+
11763
11999
  // src/handlers/signing.ts
11764
12000
  import { createHash as createHash6, randomUUID as randomUUID2, X509Certificate } from "node:crypto";
11765
12001
  import {
@@ -11771,18 +12007,18 @@ import {
11771
12007
  stat as stat4,
11772
12008
  writeFile as writeFile4
11773
12009
  } from "node:fs/promises";
11774
- import { homedir as homedir3, tmpdir as tmpdir3 } from "node:os";
12010
+ import { homedir as homedir4, tmpdir as tmpdir3 } from "node:os";
11775
12011
  import { basename as basename3, join as join5, relative as relative5, sep as sep5 } from "node:path";
11776
12012
  var PROFILE_DIRECTORIES = [
11777
12013
  join5(
11778
- homedir3(),
12014
+ homedir4(),
11779
12015
  "Library",
11780
12016
  "Developer",
11781
12017
  "Xcode",
11782
12018
  "UserData",
11783
12019
  "Provisioning Profiles"
11784
12020
  ),
11785
- join5(homedir3(), "Library", "MobileDevice", "Provisioning Profiles")
12021
+ join5(homedir4(), "Library", "MobileDevice", "Provisioning Profiles")
11786
12022
  ];
11787
12023
  var PROFILE_EXTENSIONS = [".mobileprovision", ".provisionprofile"];
11788
12024
  function text(value) {
@@ -12320,11 +12556,11 @@ var deleteSigningIdentity = async (payload, timeoutMs, signal) => {
12320
12556
  };
12321
12557
 
12322
12558
  // src/handlers/ccusage.ts
12323
- import { Buffer as Buffer2 } from "node:buffer";
12559
+ import { Buffer as Buffer3 } from "node:buffer";
12324
12560
 
12325
12561
  // src/executable-lookup.ts
12326
12562
  import { accessSync, constants } from "node:fs";
12327
- import { homedir as homedir4 } from "node:os";
12563
+ import { homedir as homedir5 } from "node:os";
12328
12564
  import { delimiter, dirname as dirname3, join as join6 } from "node:path";
12329
12565
  function isExecutable(path) {
12330
12566
  try {
@@ -12335,7 +12571,7 @@ function isExecutable(path) {
12335
12571
  }
12336
12572
  }
12337
12573
  function toolDirectories(options = {}) {
12338
- const home = options.home ?? homedir4();
12574
+ const home = options.home ?? homedir5();
12339
12575
  const platform2 = options.platform ?? process.platform;
12340
12576
  return [
12341
12577
  join6(home, ".local", "bin"),
@@ -12524,7 +12760,7 @@ async function runCcusage(payload, timeoutMs, signal, onLog) {
12524
12760
  await onLog(log);
12525
12761
  return;
12526
12762
  }
12527
- const lineBytes = Buffer2.byteLength(log.message, "utf8") + 1;
12763
+ const lineBytes = Buffer3.byteLength(log.message, "utf8") + 1;
12528
12764
  if (stdoutBytes + lineBytes > MAX_CCUSAGE_STDOUT_BYTES) {
12529
12765
  exceededLimit = true;
12530
12766
  return;
@@ -12560,7 +12796,7 @@ import {
12560
12796
  stat as stat5,
12561
12797
  statfs
12562
12798
  } from "node:fs/promises";
12563
- import { homedir as homedir5 } from "node:os";
12799
+ import { homedir as homedir6 } from "node:os";
12564
12800
  import { basename as basename4, dirname as dirname4, isAbsolute as isAbsolute4, join as join7, resolve as resolve4 } from "node:path";
12565
12801
  var successfulProcess4 = {
12566
12802
  exitCode: 0,
@@ -12578,7 +12814,7 @@ function safeRelativePath3(value) {
12578
12814
  }
12579
12815
  async function configuredRoots(payload) {
12580
12816
  if (payload.mode === "DEFAULT") {
12581
- return [join7(homedir5(), "Library", "Developer", "Xcode", "DerivedData")];
12817
+ return [join7(homedir6(), "Library", "Developer", "Xcode", "DerivedData")];
12582
12818
  }
12583
12819
  if (payload.mode === "ABSOLUTE") {
12584
12820
  if (!payload.path || !isAbsolute4(payload.path)) {
@@ -12662,7 +12898,7 @@ async function scanRoot(configuredRoot, timeoutMs, signal) {
12662
12898
  }
12663
12899
  async function scanDeviceSupport(signal) {
12664
12900
  const configuredRoot = join7(
12665
- process.env.ADE_IOS_DEVICE_SUPPORT_DIRECTORY ?? homedir5(),
12901
+ process.env.ADE_IOS_DEVICE_SUPPORT_DIRECTORY ?? homedir6(),
12666
12902
  ...process.env.ADE_IOS_DEVICE_SUPPORT_DIRECTORY ? [] : ["Library", "Developer", "Xcode", "iOS DeviceSupport"]
12667
12903
  );
12668
12904
  signal.throwIfAborted();
@@ -12956,7 +13192,7 @@ async function collectAgentDiskSpace(configuration, signal) {
12956
13192
 
12957
13193
  // src/handlers/codebases.ts
12958
13194
  import { readdir as readdir4, realpath as realpath5, stat as stat6 } from "node:fs/promises";
12959
- import { homedir as homedir6 } from "node:os";
13195
+ import { homedir as homedir7 } from "node:os";
12960
13196
  import { dirname as dirname5, join as join8, resolve as resolve5 } from "node:path";
12961
13197
  var successfulProcess5 = {
12962
13198
  exitCode: 0,
@@ -13213,7 +13449,7 @@ async function inspectCodebase(selectedFolder, timeoutMs, signal, expectedOrigin
13213
13449
  }
13214
13450
  var browseCodebaseDirectories = async (payload) => {
13215
13451
  const input = codebaseBrowsePayload(payload);
13216
- const homePath = await realpath5(homedir6());
13452
+ const homePath = await realpath5(homedir7());
13217
13453
  const path = await realpath5(input.path ? resolve5(input.path) : homePath);
13218
13454
  if (!(await stat6(path)).isDirectory())
13219
13455
  throw new Error("Path is not a directory");
@@ -13841,7 +14077,7 @@ import {
13841
14077
  stat as stat7,
13842
14078
  writeFile as writeFile5
13843
14079
  } from "node:fs/promises";
13844
- import { homedir as homedir7 } from "node:os";
14080
+ import { homedir as homedir8 } from "node:os";
13845
14081
  import { dirname as dirname6, isAbsolute as isAbsolute5, join as join9, relative as relative6, resolve as resolve6, sep as sep6 } from "node:path";
13846
14082
  import { promisify } from "node:util";
13847
14083
  var executeFile = promisify(execFile);
@@ -13903,7 +14139,7 @@ async function directoryExists(path) {
13903
14139
  }
13904
14140
  }
13905
14141
  async function configuredTools(tools) {
13906
- const homePath = homedir7();
14142
+ const homePath = homedir8();
13907
14143
  return Promise.all(
13908
14144
  AI_TOOLS.filter((tool) => tools.includes(tool)).map(async (tool) => ({
13909
14145
  tool,
@@ -13913,7 +14149,7 @@ async function configuredTools(tools) {
13913
14149
  );
13914
14150
  }
13915
14151
  function rootPath(location) {
13916
- const base = location.scope === "GLOBAL" ? homedir7() : location.folder;
14152
+ const base = location.scope === "GLOBAL" ? homedir8() : location.folder;
13917
14153
  const parts = location.scope === "GLOBAL" ? GLOBAL_ROOTS[location.rootKind] : PROJECT_ROOTS[location.rootKind];
13918
14154
  const path = resolve6(base, ...parts);
13919
14155
  const normalizedBase = resolve6(base);
@@ -14232,7 +14468,7 @@ var applySkills = async (payload) => {
14232
14468
 
14233
14469
  // src/handlers/runs.ts
14234
14470
  import { readFile as readFile6, readdir as readdir6, stat as stat8 } from "node:fs/promises";
14235
- import { homedir as homedir8 } from "node:os";
14471
+ import { homedir as homedir9 } from "node:os";
14236
14472
  import { join as join10 } from "node:path";
14237
14473
  var successfulProcess7 = {
14238
14474
  exitCode: 0,
@@ -14291,7 +14527,7 @@ async function locateSessionFile(payload, home) {
14291
14527
  }
14292
14528
  var readRunSession = async (payload) => {
14293
14529
  const input = parseRunSessionReadPayload(payload);
14294
- const path = await locateSessionFile(input, homedir8());
14530
+ const path = await locateSessionFile(input, homedir9());
14295
14531
  if (!path) {
14296
14532
  throw new Error(
14297
14533
  `No ${input.provider === "CLAUDE" ? "Claude" : "Codex"} session file found for this run on this agent`
@@ -17034,6 +17270,7 @@ var autoSyncWorktree = async (payload, timeoutMs, signal) => {
17034
17270
 
17035
17271
  // src/handlers/index.ts
17036
17272
  var handlers = {
17273
+ [CLI_HEALTH_JOB_KIND]: runCliHealth,
17037
17274
  [COMMAND_RUN_JOB_KIND]: runCommand,
17038
17275
  [CCUSAGE_REPORT_JOB_KIND]: runCcusage,
17039
17276
  [BUILD_DATA_SCAN_JOB_KIND]: scanBuildData,
@@ -17204,7 +17441,7 @@ var JobExecutor = class {
17204
17441
 
17205
17442
  // src/codebase-monitor.ts
17206
17443
  var INSPECTION_TIMEOUT_MS = 3e4;
17207
- var CONCURRENCY = 4;
17444
+ var CONCURRENCY2 = 4;
17208
17445
  var CodebaseMonitor = class {
17209
17446
  constructor(client2, repositoryCoordinator = new RepositoryCoordinator()) {
17210
17447
  this.client = client2;
@@ -17228,8 +17465,8 @@ var CodebaseMonitor = class {
17228
17465
  const codebases = configuration.codebases;
17229
17466
  const reports = [];
17230
17467
  const worktreeReports = [];
17231
- for (let index = 0; index < codebases.length; index += CONCURRENCY) {
17232
- const batch = codebases.slice(index, index + CONCURRENCY);
17468
+ for (let index = 0; index < codebases.length; index += CONCURRENCY2) {
17469
+ const batch = codebases.slice(index, index + CONCURRENCY2);
17233
17470
  const results = await Promise.all(
17234
17471
  batch.map(
17235
17472
  (codebase) => this.repositoryCoordinator.run(codebase.id, async () => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-development-environment/control-agent",
3
- "version": "0.0.88",
3
+ "version": "0.0.89",
4
4
  "description": "Control agent for the AI Development Environment",
5
5
  "license": "MIT",
6
6
  "repository": {