@controlvector/cv-agent 1.9.2 → 1.10.1

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.
package/dist/bundle.cjs CHANGED
@@ -959,8 +959,8 @@ var require_command = __commonJS({
959
959
  "node_modules/commander/lib/command.js"(exports2) {
960
960
  var EventEmitter = require("node:events").EventEmitter;
961
961
  var childProcess = require("node:child_process");
962
- var path = require("node:path");
963
- var fs3 = require("node:fs");
962
+ var path3 = require("node:path");
963
+ var fs4 = require("node:fs");
964
964
  var process3 = require("node:process");
965
965
  var { Argument: Argument2, humanReadableArgName } = require_argument();
966
966
  var { CommanderError: CommanderError2 } = require_error();
@@ -1892,11 +1892,11 @@ Expecting one of '${allowedValues.join("', '")}'`);
1892
1892
  let launchWithNode = false;
1893
1893
  const sourceExt = [".js", ".ts", ".tsx", ".mjs", ".cjs"];
1894
1894
  function findFile(baseDir, baseName) {
1895
- const localBin = path.resolve(baseDir, baseName);
1896
- if (fs3.existsSync(localBin)) return localBin;
1897
- if (sourceExt.includes(path.extname(baseName))) return void 0;
1895
+ const localBin = path3.resolve(baseDir, baseName);
1896
+ if (fs4.existsSync(localBin)) return localBin;
1897
+ if (sourceExt.includes(path3.extname(baseName))) return void 0;
1898
1898
  const foundExt = sourceExt.find(
1899
- (ext) => fs3.existsSync(`${localBin}${ext}`)
1899
+ (ext) => fs4.existsSync(`${localBin}${ext}`)
1900
1900
  );
1901
1901
  if (foundExt) return `${localBin}${foundExt}`;
1902
1902
  return void 0;
@@ -1908,21 +1908,21 @@ Expecting one of '${allowedValues.join("', '")}'`);
1908
1908
  if (this._scriptPath) {
1909
1909
  let resolvedScriptPath;
1910
1910
  try {
1911
- resolvedScriptPath = fs3.realpathSync(this._scriptPath);
1911
+ resolvedScriptPath = fs4.realpathSync(this._scriptPath);
1912
1912
  } catch (err) {
1913
1913
  resolvedScriptPath = this._scriptPath;
1914
1914
  }
1915
- executableDir = path.resolve(
1916
- path.dirname(resolvedScriptPath),
1915
+ executableDir = path3.resolve(
1916
+ path3.dirname(resolvedScriptPath),
1917
1917
  executableDir
1918
1918
  );
1919
1919
  }
1920
1920
  if (executableDir) {
1921
1921
  let localFile = findFile(executableDir, executableFile);
1922
1922
  if (!localFile && !subcommand._executableFile && this._scriptPath) {
1923
- const legacyName = path.basename(
1923
+ const legacyName = path3.basename(
1924
1924
  this._scriptPath,
1925
- path.extname(this._scriptPath)
1925
+ path3.extname(this._scriptPath)
1926
1926
  );
1927
1927
  if (legacyName !== this._name) {
1928
1928
  localFile = findFile(
@@ -1933,7 +1933,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
1933
1933
  }
1934
1934
  executableFile = localFile || executableFile;
1935
1935
  }
1936
- launchWithNode = sourceExt.includes(path.extname(executableFile));
1936
+ launchWithNode = sourceExt.includes(path3.extname(executableFile));
1937
1937
  let proc;
1938
1938
  if (process3.platform !== "win32") {
1939
1939
  if (launchWithNode) {
@@ -2773,7 +2773,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
2773
2773
  * @return {Command}
2774
2774
  */
2775
2775
  nameFromFilename(filename) {
2776
- this._name = path.basename(filename, path.extname(filename));
2776
+ this._name = path3.basename(filename, path3.extname(filename));
2777
2777
  return this;
2778
2778
  }
2779
2779
  /**
@@ -2787,9 +2787,9 @@ Expecting one of '${allowedValues.join("', '")}'`);
2787
2787
  * @param {string} [path]
2788
2788
  * @return {(string|null|Command)}
2789
2789
  */
2790
- executableDir(path2) {
2791
- if (path2 === void 0) return this._executableDir;
2792
- this._executableDir = path2;
2790
+ executableDir(path4) {
2791
+ if (path4 === void 0) return this._executableDir;
2792
+ this._executableDir = path4;
2793
2793
  return this;
2794
2794
  }
2795
2795
  /**
@@ -4117,10 +4117,138 @@ function setupCommand() {
4117
4117
 
4118
4118
  // src/commands/agent.ts
4119
4119
  var import_node_child_process5 = require("node:child_process");
4120
+ var os3 = __toESM(require("node:os"));
4121
+ var path2 = __toESM(require("node:path"));
4122
+
4123
+ // src/utils/event-queue.ts
4124
+ var import_node_fs2 = require("node:fs");
4125
+ var path = __toESM(require("node:path"));
4126
+ var EventQueue = class {
4127
+ buffer = [];
4128
+ sequence = 0;
4129
+ drainPromise = null;
4130
+ closed = false;
4131
+ spillDirty = false;
4132
+ spillPath;
4133
+ poster;
4134
+ maxRetries;
4135
+ baseDelayMs;
4136
+ onError;
4137
+ constructor(opts) {
4138
+ this.spillPath = opts.spillPath;
4139
+ this.poster = opts.poster;
4140
+ this.maxRetries = opts.maxRetries ?? 5;
4141
+ this.baseDelayMs = opts.baseDelayMs ?? 1e3;
4142
+ this.onError = opts.onError;
4143
+ }
4144
+ /** Load any events that were spilled to disk by a previous run. */
4145
+ async loadSpill() {
4146
+ try {
4147
+ const raw = await import_node_fs2.promises.readFile(this.spillPath, "utf8");
4148
+ const lines = raw.split("\n").filter((l) => l.trim().length > 0);
4149
+ for (const line of lines) {
4150
+ const ev = JSON.parse(line);
4151
+ this.buffer.push(ev);
4152
+ if (ev.sequence_number >= this.sequence) {
4153
+ this.sequence = ev.sequence_number + 1;
4154
+ }
4155
+ }
4156
+ } catch (err) {
4157
+ if (err.code !== "ENOENT") throw err;
4158
+ }
4159
+ }
4160
+ /** Assign the next sequence number. Callers that want to stamp manually can read this. */
4161
+ nextSequence() {
4162
+ return this.sequence++;
4163
+ }
4164
+ /** Enqueue an event. Returns the assigned sequence_number. */
4165
+ enqueue(event) {
4166
+ if (this.closed) {
4167
+ throw new Error("EventQueue is closed");
4168
+ }
4169
+ const seq = event.sequence_number ?? this.nextSequence();
4170
+ if (seq >= this.sequence) this.sequence = seq + 1;
4171
+ this.buffer.push({ ...event, sequence_number: seq });
4172
+ this.spillDirty = true;
4173
+ this.drain().catch(() => {
4174
+ });
4175
+ return seq;
4176
+ }
4177
+ /** Drain the buffer, retrying transient failures. Serial — concurrent calls share the same promise. */
4178
+ drain() {
4179
+ if (this.drainPromise) return this.drainPromise;
4180
+ this.drainPromise = this.drainInternal().finally(() => {
4181
+ this.drainPromise = null;
4182
+ });
4183
+ return this.drainPromise;
4184
+ }
4185
+ async drainInternal() {
4186
+ try {
4187
+ while (this.buffer.length > 0) {
4188
+ const ev = this.buffer[0];
4189
+ let delivered = false;
4190
+ for (let attempt = 0; attempt < this.maxRetries; attempt++) {
4191
+ try {
4192
+ await this.poster(ev);
4193
+ delivered = true;
4194
+ break;
4195
+ } catch (err) {
4196
+ this.onError?.(err instanceof Error ? err : new Error(String(err)), ev, attempt);
4197
+ if (attempt < this.maxRetries - 1) {
4198
+ await sleep(this.baseDelayMs * Math.pow(2, attempt));
4199
+ }
4200
+ }
4201
+ }
4202
+ if (delivered) {
4203
+ this.buffer.shift();
4204
+ this.spillDirty = true;
4205
+ } else {
4206
+ break;
4207
+ }
4208
+ }
4209
+ } finally {
4210
+ if (this.spillDirty) {
4211
+ await this.writeSpill().catch(() => {
4212
+ });
4213
+ }
4214
+ }
4215
+ }
4216
+ /** Flush: drain remaining events, then persist anything still pending to disk. */
4217
+ async flush() {
4218
+ await this.drain();
4219
+ await this.writeSpill();
4220
+ }
4221
+ /** Mark the queue closed and flush. Safe to call multiple times. */
4222
+ async close() {
4223
+ if (this.closed) return;
4224
+ this.closed = true;
4225
+ await this.flush();
4226
+ }
4227
+ /** Number of events still pending delivery. */
4228
+ size() {
4229
+ return this.buffer.length;
4230
+ }
4231
+ async writeSpill() {
4232
+ try {
4233
+ if (this.buffer.length === 0) {
4234
+ await import_node_fs2.promises.rm(this.spillPath, { force: true });
4235
+ } else {
4236
+ await import_node_fs2.promises.mkdir(path.dirname(this.spillPath), { recursive: true });
4237
+ const body = this.buffer.map((e) => JSON.stringify(e)).join("\n") + "\n";
4238
+ await import_node_fs2.promises.writeFile(this.spillPath, body, "utf8");
4239
+ }
4240
+ this.spillDirty = false;
4241
+ } catch {
4242
+ }
4243
+ }
4244
+ };
4245
+ function sleep(ms) {
4246
+ return new Promise((r) => setTimeout(r, ms));
4247
+ }
4120
4248
 
4121
4249
  // src/utils/api.ts
4122
- async function apiCall(creds, method, path, body) {
4123
- const url = `${creds.CV_HUB_API}${path}`;
4250
+ async function apiCall(creds, method, path3, body) {
4251
+ const url = `${creds.CV_HUB_API}${path3}`;
4124
4252
  const headers = {
4125
4253
  "Authorization": `Bearer ${creds.CV_HUB_PAT}`,
4126
4254
  "Content-Type": "application/json"
@@ -4131,7 +4259,7 @@ async function apiCall(creds, method, path, body) {
4131
4259
  body: body ? JSON.stringify(body) : void 0
4132
4260
  });
4133
4261
  }
4134
- async function registerExecutor(creds, machineName, workingDir, repositoryId, metadata) {
4262
+ async function registerExecutor(creds, machineName, workingDir, repositoryId, metadata, repoOwnerSlug) {
4135
4263
  const body = {
4136
4264
  name: `cva:${machineName}`,
4137
4265
  machine_name: machineName,
@@ -4150,7 +4278,23 @@ async function registerExecutor(creds, machineName, workingDir, repositoryId, me
4150
4278
  if (metadata?.tags) body.tags = metadata.tags;
4151
4279
  if (metadata?.owner_project) body.owner_project = metadata.owner_project;
4152
4280
  if (metadata?.integration) body.integration = metadata.integration;
4153
- const res = await apiCall(creds, "POST", "/api/v1/executors", body);
4281
+ let res = await apiCall(creds, "POST", "/api/v1/executors", body);
4282
+ if (res.status === 400 && repoOwnerSlug) {
4283
+ try {
4284
+ const errData = await res.json();
4285
+ const orgs = errData.error?.organizations;
4286
+ if (orgs && orgs.length > 0) {
4287
+ const match = orgs.find(
4288
+ (o) => o.slug.toLowerCase() === repoOwnerSlug.toLowerCase()
4289
+ );
4290
+ if (match) {
4291
+ body.organization_id = match.id;
4292
+ res = await apiCall(creds, "POST", "/api/v1/executors", body);
4293
+ }
4294
+ }
4295
+ } catch {
4296
+ }
4297
+ }
4154
4298
  if (!res.ok) {
4155
4299
  const err = await res.text();
4156
4300
  throw new Error(`Failed to register executor: ${res.status} ${err}`);
@@ -4593,7 +4737,7 @@ async function writeConfig(config) {
4593
4737
 
4594
4738
  // src/commands/git-safety.ts
4595
4739
  var import_node_child_process3 = require("node:child_process");
4596
- var import_node_fs2 = require("node:fs");
4740
+ var import_node_fs3 = require("node:fs");
4597
4741
  var import_node_path2 = require("node:path");
4598
4742
  var import_node_os3 = require("node:os");
4599
4743
  var DANGEROUS_PATTERNS = [
@@ -4629,7 +4773,7 @@ function checkWorkspaceSafety(workspaceRoot) {
4629
4773
  if (home.startsWith(resolved + "/")) {
4630
4774
  return `Workspace (${resolved}) is a parent of HOME. Refusing to auto-commit.`;
4631
4775
  }
4632
- if (!(0, import_node_fs2.existsSync)((0, import_node_path2.join)(resolved, ".git"))) {
4776
+ if (!(0, import_node_fs3.existsSync)((0, import_node_path2.join)(resolved, ".git"))) {
4633
4777
  return `No .git directory in ${resolved}. Not a git repository.`;
4634
4778
  }
4635
4779
  return null;
@@ -4771,7 +4915,7 @@ Files: ${added} added, ${modified} modified, ${deleted} deleted`;
4771
4915
 
4772
4916
  // src/commands/deploy-manifest.ts
4773
4917
  var import_node_child_process4 = require("node:child_process");
4774
- var import_node_fs3 = require("node:fs");
4918
+ var import_node_fs4 = require("node:fs");
4775
4919
  var import_node_path3 = require("node:path");
4776
4920
  function exec(cmd, cwd, timeoutMs = 3e5, env2) {
4777
4921
  try {
@@ -4816,9 +4960,9 @@ function getHeadCommit(cwd) {
4816
4960
  }
4817
4961
  function loadDeployManifest(workspaceRoot) {
4818
4962
  const manifestPath = (0, import_node_path3.join)(workspaceRoot, ".cva", "deploy.json");
4819
- if (!(0, import_node_fs3.existsSync)(manifestPath)) return null;
4963
+ if (!(0, import_node_fs4.existsSync)(manifestPath)) return null;
4820
4964
  try {
4821
- const raw = (0, import_node_fs3.readFileSync)(manifestPath, "utf8");
4965
+ const raw = (0, import_node_fs4.readFileSync)(manifestPath, "utf8");
4822
4966
  const manifest = JSON.parse(raw);
4823
4967
  if (!manifest.version || manifest.version < 1) {
4824
4968
  console.log(" [deploy] Invalid manifest version");
@@ -5185,6 +5329,7 @@ async function launchAutoApproveMode(prompt, options) {
5185
5329
  const pendingQuestionIds = [];
5186
5330
  let fullOutput = "";
5187
5331
  let lastProgressBytes = 0;
5332
+ let totalBytesStreamed = 0;
5188
5333
  const runOnce = (inputPrompt, isContinue) => {
5189
5334
  return new Promise((resolve2, reject) => {
5190
5335
  const args = isContinue ? ["-p", inputPrompt, "--continue", "--allowedTools", ...ALLOWED_TOOLS] : ["-p", inputPrompt, "--allowedTools", ...ALLOWED_TOOLS];
@@ -5206,8 +5351,20 @@ async function launchAutoApproveMode(prompt, options) {
5206
5351
  process.stdout.write(data);
5207
5352
  fullOutput += text;
5208
5353
  if (fullOutput.length > MAX_OUTPUT_BYTES) {
5354
+ const dropped = fullOutput.length - MAX_OUTPUT_BYTES;
5209
5355
  fullOutput = fullOutput.slice(-MAX_OUTPUT_BYTES);
5356
+ options.eventQueue?.enqueue({
5357
+ event_type: "output",
5358
+ content: {
5359
+ chunk: `
5360
+ [... ${dropped} bytes truncated from output buffer ...]
5361
+ `,
5362
+ truncated_bytes: dropped,
5363
+ byte_offset: totalBytesStreamed
5364
+ }
5365
+ });
5210
5366
  }
5367
+ totalBytesStreamed += text.length;
5211
5368
  if (Date.now() - spawnTime < 1e4) {
5212
5369
  const authError = containsAuthError(fullOutput + stderr);
5213
5370
  if (authError) {
@@ -5258,10 +5415,9 @@ ${source_default.red("!")} Claude Code auth failure detected: "${authError}"`);
5258
5415
  ).catch(() => {
5259
5416
  });
5260
5417
  }
5261
- postTaskEvent(options.creds, options.taskId, {
5418
+ options.eventQueue?.enqueue({
5262
5419
  event_type: "output",
5263
- content: { chunk, byte_offset: lastProgressBytes }
5264
- }).catch(() => {
5420
+ content: { chunk, byte_offset: totalBytesStreamed }
5265
5421
  });
5266
5422
  }
5267
5423
  }
@@ -5337,6 +5493,7 @@ async function launchRelayMode(prompt, options) {
5337
5493
  let lastProgressBytes = 0;
5338
5494
  let authFailure = false;
5339
5495
  const spawnTime = Date.now();
5496
+ let totalBytesStreamed = 0;
5340
5497
  child.stdin?.write(prompt + "\n");
5341
5498
  let lastRedirectCheck = Date.now();
5342
5499
  let lineBuffer = "";
@@ -5346,8 +5503,20 @@ async function launchRelayMode(prompt, options) {
5346
5503
  stdoutBuffer += text;
5347
5504
  fullOutput += text;
5348
5505
  if (fullOutput.length > MAX_OUTPUT_BYTES) {
5506
+ const dropped = fullOutput.length - MAX_OUTPUT_BYTES;
5349
5507
  fullOutput = fullOutput.slice(-MAX_OUTPUT_BYTES);
5508
+ options.eventQueue?.enqueue({
5509
+ event_type: "output",
5510
+ content: {
5511
+ chunk: `
5512
+ [... ${dropped} bytes truncated from output buffer ...]
5513
+ `,
5514
+ truncated_bytes: dropped,
5515
+ byte_offset: totalBytesStreamed
5516
+ }
5517
+ });
5350
5518
  }
5519
+ totalBytesStreamed += text.length;
5351
5520
  if (Date.now() - spawnTime < 1e4 && !authFailure) {
5352
5521
  const authError = containsAuthError(fullOutput + stderr);
5353
5522
  if (authError) {
@@ -5406,10 +5575,9 @@ ${source_default.red("!")} Claude Code auth failure detected: "${authError}"`);
5406
5575
  { output_chunk: chunk }
5407
5576
  ).catch(() => {
5408
5577
  });
5409
- postTaskEvent(options.creds, options.taskId, {
5578
+ options.eventQueue?.enqueue({
5410
5579
  event_type: "output",
5411
- content: { chunk, byte_offset: lastProgressBytes }
5412
- }).catch(() => {
5580
+ content: { chunk, byte_offset: totalBytesStreamed }
5413
5581
  });
5414
5582
  }
5415
5583
  if (Date.now() - lastRedirectCheck > 1e4) {
@@ -5727,16 +5895,16 @@ async function runAgent(options) {
5727
5895
  }
5728
5896
  const gitignorePath = require("path").join(workingDir, ".gitignore");
5729
5897
  try {
5730
- const fs3 = require("fs");
5898
+ const fs4 = require("fs");
5731
5899
  const credPatterns = ".env\n.env.*\n.claude/\n.claude.json\n.credentials*\n*.pem\n*.key\n.ssh/\n.gnupg/\n.npm/\n.config/\n.zsh_history\n.bash_history\nnode_modules/\n.DS_Store\n";
5732
5900
  let existing = "";
5733
5901
  try {
5734
- existing = fs3.readFileSync(gitignorePath, "utf-8");
5902
+ existing = fs4.readFileSync(gitignorePath, "utf-8");
5735
5903
  } catch {
5736
5904
  }
5737
5905
  if (!existing.includes(".claude/")) {
5738
5906
  const prefix = existing && !existing.endsWith("\n") ? "\n" : "";
5739
- fs3.writeFileSync(gitignorePath, existing + prefix + "# Credentials (auto-added by cv-agent)\n" + credPatterns);
5907
+ fs4.writeFileSync(gitignorePath, existing + prefix + "# Credentials (auto-added by cv-agent)\n" + credPatterns);
5740
5908
  console.log(source_default.gray(" Bootstrap: .gitignore credential protection added"));
5741
5909
  }
5742
5910
  } catch {
@@ -5774,6 +5942,7 @@ async function runAgent(options) {
5774
5942
  }
5775
5943
  }
5776
5944
  let detectedRepoId;
5945
+ let detectedOwnerSlug;
5777
5946
  try {
5778
5947
  const remoteUrl = (0, import_node_child_process5.execSync)("git remote get-url origin 2>/dev/null", {
5779
5948
  cwd: workingDir,
@@ -5785,6 +5954,7 @@ async function runAgent(options) {
5785
5954
  );
5786
5955
  if (cvHubMatch) {
5787
5956
  const [, repoOwner, repoSlug] = cvHubMatch;
5957
+ detectedOwnerSlug = repoOwner;
5788
5958
  try {
5789
5959
  const repoData = await resolveRepoId(creds, repoOwner, repoSlug);
5790
5960
  if (repoData?.id) {
@@ -5796,6 +5966,9 @@ async function runAgent(options) {
5796
5966
  }
5797
5967
  } catch {
5798
5968
  }
5969
+ if (options.org) {
5970
+ detectedOwnerSlug = options.org;
5971
+ }
5799
5972
  const wsConfig = await readWorkspaceConfig(workingDir);
5800
5973
  const globalConfig = await readConfig();
5801
5974
  const role = options.role || wsConfig.role || globalConfig.role || "development";
@@ -5836,7 +6009,7 @@ async function runAgent(options) {
5836
6009
  console.log(source_default.yellow(` Guard: ${guardIcon} ${dispatchGuard}`));
5837
6010
  }
5838
6011
  const executor = await withRetry(
5839
- () => registerExecutor(creds, machineName, workingDir, detectedRepoId, executorMeta),
6012
+ () => registerExecutor(creds, machineName, workingDir, detectedRepoId, executorMeta, detectedOwnerSlug),
5840
6013
  "Executor registration"
5841
6014
  );
5842
6015
  const mode = options.autoApprove ? "auto-approve" : "relay";
@@ -5956,6 +6129,14 @@ ${source_default.red("Timeout")} Task timed out after ${formatDuration(timeoutMs
5956
6129
  console.log(source_default.gray(` Git remote: ${remoteInfo.remoteName} -> ${remoteInfo.remoteUrl}`));
5957
6130
  }
5958
6131
  const prompt = buildClaudePrompt(task);
6132
+ const spillPath = path2.join(os3.tmpdir(), "cva-event-queue", `${task.id}.ndjson`);
6133
+ const eventQueue = new EventQueue({
6134
+ spillPath,
6135
+ poster: async (event) => {
6136
+ await postTaskEvent(creds, task.id, event);
6137
+ }
6138
+ });
6139
+ await eventQueue.loadSpill();
5959
6140
  try {
5960
6141
  const mode = options.autoApprove ? "auto-approve" : "relay";
5961
6142
  console.log(`\u{1F680} ${source_default.bold.green("RUNNING")} \u2014 Launching Claude Code (${mode})...`);
@@ -5971,7 +6152,8 @@ ${source_default.red("Timeout")} Task timed out after ${formatDuration(timeoutMs
5971
6152
  taskId: task.id,
5972
6153
  executorId: state.executorId,
5973
6154
  spawnEnv: claudeEnv,
5974
- machineName: state.machineName
6155
+ machineName: state.machineName,
6156
+ eventQueue
5975
6157
  });
5976
6158
  } else {
5977
6159
  result = await launchRelayMode(prompt, {
@@ -5980,7 +6162,8 @@ ${source_default.red("Timeout")} Task timed out after ${formatDuration(timeoutMs
5980
6162
  executorId: state.executorId,
5981
6163
  taskId: task.id,
5982
6164
  spawnEnv: claudeEnv,
5983
- machineName: state.machineName
6165
+ machineName: state.machineName,
6166
+ eventQueue
5984
6167
  });
5985
6168
  }
5986
6169
  console.log(source_default.gray("\n" + "-".repeat(60)));
@@ -6041,14 +6224,13 @@ ${source_default.red("Timeout")} Task timed out after ${formatDuration(timeoutMs
6041
6224
  sendTaskLog(creds, state.executorId, task.id, "info", `Deploy manifest error: ${deployErr.message}`);
6042
6225
  }
6043
6226
  }
6044
- postTaskEvent(creds, task.id, {
6227
+ eventQueue.enqueue({
6045
6228
  event_type: "completed",
6046
6229
  content: {
6047
6230
  exit_code: result.exitCode,
6048
6231
  duration_seconds: Math.round((Date.now() - startTime) / 1e3),
6049
6232
  files_changed: allChangedFiles.length
6050
6233
  }
6051
- }).catch(() => {
6052
6234
  });
6053
6235
  if (result.exitCode === 0) {
6054
6236
  if (allChangedFiles.length > 0) {
@@ -6073,14 +6255,13 @@ ${source_default.red("Timeout")} Task timed out after ${formatDuration(timeoutMs
6073
6255
  console.log();
6074
6256
  console.log(`\u2705 ${source_default.bold.green("COMPLETED")} \u2014 Duration: ${elapsed}`);
6075
6257
  printBanner("COMPLETED", elapsed, allChangedFiles, postGitState.headSha);
6076
- postTaskEvent(creds, task.id, {
6258
+ eventQueue.enqueue({
6077
6259
  event_type: "output_final",
6078
6260
  content: {
6079
6261
  output: result.output.slice(-MAX_OUTPUT_FINAL_BYTES),
6080
6262
  exit_code: result.exitCode,
6081
6263
  duration_seconds: Math.round((Date.now() - startTime) / 1e3)
6082
6264
  }
6083
- }).catch(() => {
6084
6265
  });
6085
6266
  await withRetry(
6086
6267
  () => completeTask(creds, state.executorId, task.id, payload),
@@ -6169,6 +6350,10 @@ ${source_default.red("!")} Task error: ${err.message}`);
6169
6350
  } finally {
6170
6351
  clearInterval(heartbeatTimer);
6171
6352
  if (timeoutTimer) clearTimeout(timeoutTimer);
6353
+ try {
6354
+ await eventQueue.close();
6355
+ } catch {
6356
+ }
6172
6357
  state.currentTaskId = null;
6173
6358
  state.lastTaskEnd = Date.now();
6174
6359
  }
@@ -6192,6 +6377,7 @@ function agentCommand() {
6192
6377
  cmd.option("--dispatch-guard <guard>", "Dispatch guard: open, confirm, locked (default: open)");
6193
6378
  cmd.option("--tags <tags>", "Comma-separated tags for this executor");
6194
6379
  cmd.option("--owner-project <project>", "Project this executor belongs to");
6380
+ cmd.option("--org <slug>", "Organization slug (auto-detected from repo owner if omitted)");
6195
6381
  cmd.action(async (opts) => {
6196
6382
  await runAgent(opts);
6197
6383
  });
@@ -6515,7 +6701,7 @@ function statusCommand() {
6515
6701
 
6516
6702
  // src/index.ts
6517
6703
  var program2 = new Command();
6518
- program2.name("cva").description('CV-Hub Agent \u2014 bridges Claude Code with CV-Hub task dispatch.\n\nRun "cva setup" to get started.').version(true ? "1.9.2" : "1.6.0");
6704
+ program2.name("cva").description('CV-Hub Agent \u2014 bridges Claude Code with CV-Hub task dispatch.\n\nRun "cva setup" to get started.').version(true ? "1.10.1" : "1.6.0");
6519
6705
  program2.addCommand(setupCommand());
6520
6706
  program2.addCommand(agentCommand());
6521
6707
  program2.addCommand(authCommand());