ai-whisper 0.8.1 → 0.10.0

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/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # ai-whisper
2
2
 
3
- ai-whisper pairs two coding agents — mount any two of Claude, Codex, and ezio — into a terminal-native pair that hand work back and forth under a single baton, so one agent implements while the other reviews, and a structured workflow drives the loop to a finished, reviewed deliverable without a human babysitting every round.
3
+ ai-whisper pairs two coding agents — mount any two of Claude, Codex, ezio, and agy — into a terminal-native pair that hand work back and forth under a single baton, so one agent implements while the other reviews, and a structured workflow drives the loop to a finished, reviewed deliverable without a human babysitting every round.
4
4
 
5
5
  ## Magic moment
6
6
 
@@ -52,13 +52,14 @@ It is **not** for:
52
52
 
53
53
  ## Prerequisites
54
54
 
55
- You pair any two of three agents — `claude`, `codex`, and `ezio`. ai-whisper drives the *real* Claude and Codex CLIs, so install and authenticate whichever of those two you plan to mount first; `ezio` is protocol-native and ships with ai-whisper, so it needs no separate CLI.
55
+ You pair any two of four agents — `claude`, `codex`, `ezio`, and `agy`. ai-whisper drives the *real* Claude, Codex, and Antigravity CLIs, so install and authenticate whichever of those you plan to mount first; `ezio` is protocol-native and ships with ai-whisper.
56
56
 
57
57
  - **[Claude Code CLI](https://claude.com/claude-code)** — the `claude` command, signed in.
58
58
  - **[Codex CLI](https://github.com/openai/codex)** — the `codex` command, signed in.
59
59
  - **ezio** *(optional)* — bundled with ai-whisper; mount it with `whisper collab mount ezio`, no separate install.
60
+ - **agy** *(optional)* — the `agy` command (Antigravity CLI), signed in. Mount it with `whisper collab mount agy` (manual-mount parity; no auto-launch via `collab start`).
60
61
  - **Node.js 22+**.
61
- - **An LLM evaluator with credentials** — workflows are gated by it and refuse to start without it. See [Evaluator configuration](docs/evaluator-configuration.md).
62
+ - **An LLM evaluator with credentials** — workflows are gated by it and refuse to start without it. It supports four providers: **Anthropic** (default), a local **Ollama** model, **OpenAI** (and any OpenAI-compatible backend via a `baseURL` — Azure, OpenRouter, vLLM, LM Studio, …), or reusing an **already-mounted agent CLI** (`claude`/`codex`/`agy`) in non-interactive mode (no separate key — it reuses that CLI's own auth). See [Evaluator configuration](docs/evaluator-configuration.md).
62
63
  - **tmux** *(optional)* — only for `whisper collab start`, which auto-arranges both agents into panes. The mount flow below does not need it.
63
64
 
64
65
  > **Platform support:** ai-whisper is terminal-native and Unix-oriented — it drives interactive PTY sessions, so it runs on **macOS and Linux**. It is **not supported natively on Windows**: `whisper collab mount` / `reconnect` require a Unix tty-backed shell and will exit with an error pointing here. On Windows, run ai-whisper inside **[WSL2](https://learn.microsoft.com/windows/wsl/install)** — install Node, your agent CLI, and ai-whisper inside the WSL2 distro and run the commands there, where everything works as-is.
@@ -120,9 +121,9 @@ A run that stops short usually **escalates** — it does not crash. When the eva
120
121
 
121
122
  ## Core concepts
122
123
 
123
- ai-whisper is **not a swarm**. The agents never type at once — work moves by a single baton, one owner at a time. Mounted sessions are *real* agent sessions in your terminal — Claude or Codex CLIs, or ezio — and those sessions are the source of truth. Autonomy is supervised: every handoff, verdict, and round is inspectable, and runs are resumable rather than fire-and-forget. Work is organized as structured workflows — explicit loops and state transitions, not a free-form chat.
124
+ ai-whisper is **not a swarm**. The agents never type at once — work moves by a single baton, one owner at a time. Mounted sessions are *real* agent sessions in your terminal — Claude or Codex CLIs, ezio, or agy — and those sessions are the source of truth. Autonomy is supervised: every handoff, verdict, and round is inspectable, and runs are resumable rather than fire-and-forget. Work is organized as structured workflows — explicit loops and state transitions, not a free-form chat.
124
125
 
125
- Claude, Codex, and ezio are supported today — you mount any two of them; the architecture is provider-agnostic by design, so other coding-agent CLIs can be added behind the same relay.
126
+ Claude, Codex, ezio, and agy are supported today — you mount any two of them; the architecture is provider-agnostic by design, so other coding-agent CLIs can be added behind the same relay.
126
127
 
127
128
  For the full mental model, read [Concepts](docs/concepts.md).
128
129
 
@@ -63,7 +63,7 @@ var threadStates = [
63
63
  "failed"
64
64
  ];
65
65
  var collabStates = ["active", "stopped"];
66
- var agentTypes = ["codex", "claude", "ezio"];
66
+ var agentTypes = ["codex", "claude", "ezio", "agy"];
67
67
  var sessionRegistrationStates = ["registered"];
68
68
  var workItemStates = [
69
69
  "queued",
@@ -332,7 +332,7 @@ var mockProviderReplySchema = z14.object({
332
332
 
333
333
  // ../shared/dist/relay-host.js
334
334
  import { z as z15 } from "zod";
335
- var relayTargets = ["codex", "claude", "ezio", "pull"];
335
+ var relayTargets = ["codex", "claude", "ezio", "agy", "pull"];
336
336
  var relayDirectiveSchema = z15.object({
337
337
  raw: z15.string().min(1),
338
338
  target: z15.enum(relayTargets),
@@ -5918,7 +5918,45 @@ function createWorkflowEventBridge(input) {
5918
5918
  import * as crypto3 from "node:crypto";
5919
5919
  import Anthropic from "@anthropic-ai/sdk";
5920
5920
  import { Ollama } from "ollama";
5921
+ import OpenAI from "openai";
5921
5922
  import { z as z18 } from "zod";
5923
+
5924
+ // src/runtime/agent-cli-presets.ts
5925
+ import { spawn as nodeSpawn } from "node:child_process";
5926
+ import { accessSync, constants, statSync as statSync3 } from "node:fs";
5927
+ import { delimiter, join as join6 } from "node:path";
5928
+ var defaultSpawn = (command, args, options) => nodeSpawn(command, [...args], options);
5929
+ var PRESETS = {
5930
+ claude: { executable: "claude", execArgs: ["-p"], promptVia: "arg" },
5931
+ codex: { executable: "codex", execArgs: ["exec"], promptVia: "arg" },
5932
+ agy: { executable: "agy", execArgs: ["-p"], promptVia: "arg" }
5933
+ };
5934
+ function resolveAgentCliInvocation(input) {
5935
+ const preset = PRESETS[input.agent];
5936
+ return {
5937
+ executable: input.executable ?? preset.executable,
5938
+ execArgs: input.execArgs ?? preset.execArgs,
5939
+ promptVia: input.promptVia ?? preset.promptVia
5940
+ };
5941
+ }
5942
+ function isExecutableOnPath(executable) {
5943
+ const isExecFile = (p) => {
5944
+ try {
5945
+ if (!statSync3(p).isFile()) return false;
5946
+ accessSync(p, constants.X_OK);
5947
+ return true;
5948
+ } catch {
5949
+ return false;
5950
+ }
5951
+ };
5952
+ if (executable.includes("/") || executable.includes("\\")) return isExecFile(executable);
5953
+ const dirs = (process.env.PATH ?? "").split(delimiter).filter(Boolean);
5954
+ const exts = process.platform === "win32" ? ["", ".exe", ".cmd", ".bat"] : [""];
5955
+ return dirs.some((dir) => exts.some((ext) => isExecFile(join6(dir, executable + ext))));
5956
+ }
5957
+
5958
+ // src/runtime/relay-orchestrator-evaluator.ts
5959
+ var defaultOpenAIFactory = (opts) => new OpenAI(opts);
5922
5960
  var baseFields = {
5923
5961
  confidence: z18.number().min(0).max(1),
5924
5962
  reason: z18.string()
@@ -6158,7 +6196,10 @@ function selectBranch(payload) {
6158
6196
  }
6159
6197
  return legacyBranch;
6160
6198
  }
6199
+ var ProviderUnavailableError = class extends Error {
6200
+ };
6161
6201
  function isProviderUnavailableError(error) {
6202
+ if (error instanceof ProviderUnavailableError) return true;
6162
6203
  if (!(error instanceof Error)) return false;
6163
6204
  const code = error.code;
6164
6205
  if (code === "ECONNREFUSED" || code === "ENOTFOUND" || code === "ETIMEDOUT" || code === "ECONNRESET")
@@ -6222,6 +6263,77 @@ function buildOllamaCaller(config) {
6222
6263
  return { raw: response.message.content };
6223
6264
  };
6224
6265
  }
6266
+ function buildOpenAICaller(config) {
6267
+ const client = (config.createClient ?? defaultOpenAIFactory)({
6268
+ apiKey: config.apiKey,
6269
+ ...config.baseURL ? { baseURL: config.baseURL } : {}
6270
+ });
6271
+ const model = config.model;
6272
+ return async function(systemPrompt, payload, jsonSchema) {
6273
+ const response = await client.chat.completions.create({
6274
+ model,
6275
+ messages: [
6276
+ { role: "system", content: systemPrompt },
6277
+ { role: "user", content: JSON.stringify(payload) }
6278
+ ],
6279
+ // Non-strict json_schema: schema-shaped guidance only; branch.parse() is authoritative.
6280
+ // `strict` is intentionally omitted (see spec §5.2.1) — do not set it true.
6281
+ response_format: { type: "json_schema", json_schema: { name: "verdict", schema: jsonSchema } },
6282
+ temperature: 0.3
6283
+ });
6284
+ const raw = response.choices[0]?.message.content ?? "";
6285
+ return {
6286
+ raw,
6287
+ ...typeof response.usage?.prompt_tokens === "number" ? { inputTokens: response.usage.prompt_tokens } : {},
6288
+ ...typeof response.usage?.completion_tokens === "number" ? { outputTokens: response.usage.completion_tokens } : {}
6289
+ };
6290
+ };
6291
+ }
6292
+ function buildAgentCliCaller(config) {
6293
+ const { executable, execArgs, promptVia } = resolveAgentCliInvocation(config);
6294
+ const spawnImpl = config.spawnImpl ?? defaultSpawn;
6295
+ return function(systemPrompt, payload) {
6296
+ const prompt = `${systemPrompt}
6297
+
6298
+ === INPUT (JSON) ===
6299
+ ${JSON.stringify(payload)}
6300
+ === END INPUT ===
6301
+
6302
+ Reply with ONLY the JSON object described above \u2014 no prose, no code fence.`;
6303
+ return new Promise((resolve2, reject) => {
6304
+ const child = promptVia === "arg" ? spawnImpl(executable, [...execArgs, prompt], { stdio: ["ignore", "pipe", "pipe"] }) : spawnImpl(executable, execArgs, { stdio: ["pipe", "pipe", "pipe"] });
6305
+ let stdout = "";
6306
+ let stderr = "";
6307
+ let settled = false;
6308
+ child.stdout.on("data", (chunk) => {
6309
+ stdout += String(chunk);
6310
+ });
6311
+ child.stderr.on("data", (chunk) => {
6312
+ stderr += String(chunk);
6313
+ });
6314
+ child.on("error", (err) => {
6315
+ if (settled) return;
6316
+ settled = true;
6317
+ reject(new ProviderUnavailableError(`agent-cli spawn failed (${executable}): ${err.message}`));
6318
+ });
6319
+ child.on("close", (code) => {
6320
+ if (settled) return;
6321
+ settled = true;
6322
+ if (code !== 0) {
6323
+ reject(new ProviderUnavailableError(`agent-cli ${executable} exited with code ${code}: ${stderr.trim()}`));
6324
+ return;
6325
+ }
6326
+ resolve2({ raw: stdout });
6327
+ });
6328
+ if (promptVia === "stdin") {
6329
+ child.stdin.on("error", () => {
6330
+ });
6331
+ child.stdin.write(prompt);
6332
+ child.stdin.end();
6333
+ }
6334
+ });
6335
+ };
6336
+ }
6225
6337
  function buildSingleProviderCaller(config) {
6226
6338
  if (config.provider === "anthropic") {
6227
6339
  const call2 = buildAnthropicCaller(config);
@@ -6264,6 +6376,46 @@ function buildSingleProviderCaller(config) {
6264
6376
  }
6265
6377
  };
6266
6378
  }
6379
+ if (config.provider === "openai") {
6380
+ const call2 = buildOpenAICaller(config);
6381
+ return async function(payload, branch) {
6382
+ const started = Date.now();
6383
+ let providerResult;
6384
+ try {
6385
+ providerResult = await call2(branch.systemPrompt, payload, branch.jsonSchema);
6386
+ } catch (callErr) {
6387
+ const providerLatencyMs2 = Date.now() - started;
6388
+ return { ok: false, error: callErr instanceof Error ? callErr : new Error(String(callErr)), raw: null, inputTokens: null, outputTokens: null, providerLatencyMs: providerLatencyMs2 };
6389
+ }
6390
+ const providerLatencyMs = Date.now() - started;
6391
+ try {
6392
+ const verdict = branch.parse(providerResult.raw);
6393
+ return { ok: true, verdict, raw: providerResult.raw, inputTokens: providerResult.inputTokens ?? null, outputTokens: providerResult.outputTokens ?? null, providerLatencyMs };
6394
+ } catch (parseErr) {
6395
+ return { ok: false, error: parseErr instanceof Error ? parseErr : new Error(String(parseErr)), raw: providerResult.raw, inputTokens: providerResult.inputTokens ?? null, outputTokens: providerResult.outputTokens ?? null, providerLatencyMs };
6396
+ }
6397
+ };
6398
+ }
6399
+ if (config.provider === "agent-cli") {
6400
+ const call2 = buildAgentCliCaller(config);
6401
+ return async function(payload, branch) {
6402
+ const started = Date.now();
6403
+ let raw;
6404
+ try {
6405
+ ({ raw } = await call2(branch.systemPrompt, payload));
6406
+ } catch (callErr) {
6407
+ const providerLatencyMs2 = Date.now() - started;
6408
+ return { ok: false, error: callErr instanceof Error ? callErr : new Error(String(callErr)), raw: null, inputTokens: null, outputTokens: null, providerLatencyMs: providerLatencyMs2 };
6409
+ }
6410
+ const providerLatencyMs = Date.now() - started;
6411
+ try {
6412
+ const verdict = branch.parse(raw);
6413
+ return { ok: true, verdict, raw, inputTokens: null, outputTokens: null, providerLatencyMs };
6414
+ } catch (parseErr) {
6415
+ return { ok: false, error: parseErr instanceof Error ? parseErr : new Error(String(parseErr)), raw, inputTokens: null, outputTokens: null, providerLatencyMs };
6416
+ }
6417
+ };
6418
+ }
6267
6419
  const call = buildOllamaCaller(config);
6268
6420
  return async function(payload, branch) {
6269
6421
  const started = Date.now();
@@ -6710,8 +6862,8 @@ function writeOwnPidToBrokerDaemon(db, input) {
6710
6862
  }
6711
6863
 
6712
6864
  // src/runtime/evaluator-config.ts
6713
- import { readFileSync as readFileSync2, statSync as statSync3 } from "node:fs";
6714
- import { join as join6 } from "node:path";
6865
+ import { readFileSync as readFileSync2, statSync as statSync4 } from "node:fs";
6866
+ import { join as join7 } from "node:path";
6715
6867
 
6716
6868
  // src/runtime/state-root.ts
6717
6869
  import os from "node:os";
@@ -6745,7 +6897,7 @@ function warnIfLoosePerms(path2) {
6745
6897
  if (process.platform === "win32") return;
6746
6898
  let mode;
6747
6899
  try {
6748
- mode = statSync3(path2).mode;
6900
+ mode = statSync4(path2).mode;
6749
6901
  } catch {
6750
6902
  return;
6751
6903
  }
@@ -6773,31 +6925,46 @@ function loadEvaluatorConfig() {
6773
6925
  const root = getStateRoot();
6774
6926
  let dotenv = {};
6775
6927
  try {
6776
- dotenv = parseDotEnv(readFileSync2(join6(root, ".env"), "utf8"));
6928
+ dotenv = parseDotEnv(readFileSync2(join7(root, ".env"), "utf8"));
6777
6929
  } catch (err) {
6778
6930
  if (err.code !== "ENOENT") throw err;
6779
6931
  }
6780
6932
  const envGet = (k) => process.env[k] ?? dotenv[k];
6781
- const authPath = join6(root, "auth.json");
6933
+ const authPath = join7(root, "auth.json");
6782
6934
  warnIfLoosePerms(authPath);
6783
6935
  const auth = readJsonFile(authPath, "auth.json");
6784
- const config = readJsonFile(join6(root, "config.json"), "config.json");
6936
+ const config = readJsonFile(join7(root, "config.json"), "config.json");
6785
6937
  const evalCfg = config?.evaluator ?? {};
6786
6938
  const evalOllama = evalCfg.ollama ?? {};
6787
- const provider = (envGet("AI_WHISPER_EVALUATOR_PROVIDER") ?? evalCfg.provider) === "ollama" ? "ollama" : "anthropic";
6939
+ const provider = envGet("AI_WHISPER_EVALUATOR_PROVIDER") ?? evalCfg.provider;
6940
+ const resolvedProvider = provider === "ollama" || provider === "openai" || provider === "agent-cli" ? provider : "anthropic";
6788
6941
  const rawFallback = envGet("AI_WHISPER_EVALUATOR_FALLBACK") ?? evalCfg.fallback;
6789
- const fallback = rawFallback === "anthropic" || rawFallback === "ollama" ? rawFallback : null;
6942
+ const fallback = rawFallback === "anthropic" || rawFallback === "ollama" || rawFallback === "openai" || rawFallback === "agent-cli" ? rawFallback : null;
6790
6943
  const apiKey = envGet("ANTHROPIC_API_KEY") ?? auth?.ANTHROPIC_API_KEY ?? null;
6944
+ const evalOpenai = evalCfg.openai ?? {};
6945
+ const openaiKey = envGet("OPENAI_API_KEY") ?? auth?.OPENAI_API_KEY ?? null;
6946
+ const evalAgentCli = evalCfg.agentCli ?? {};
6947
+ const rawAgent = envGet("AI_WHISPER_EVALUATOR_AGENT_CLI_AGENT") ?? evalAgentCli.agent;
6948
+ const agent = rawAgent === "claude" || rawAgent === "codex" || rawAgent === "agy" ? rawAgent : null;
6791
6949
  return {
6792
- provider,
6950
+ provider: resolvedProvider,
6793
6951
  fallback,
6794
- anthropic: {
6795
- apiKey: apiKey && apiKey.length > 0 ? apiKey : null,
6796
- model: evalCfg.anthropicModel ?? null
6797
- },
6952
+ anthropic: { apiKey: apiKey && apiKey.length > 0 ? apiKey : null, model: evalCfg.anthropicModel ?? null },
6798
6953
  ollama: {
6799
6954
  host: envGet("AI_WHISPER_EVALUATOR_OLLAMA_HOST") ?? evalOllama.host ?? null,
6800
6955
  model: envGet("AI_WHISPER_EVALUATOR_OLLAMA_MODEL") ?? evalOllama.model ?? null
6956
+ },
6957
+ openai: {
6958
+ apiKey: openaiKey && openaiKey.length > 0 ? openaiKey : null,
6959
+ model: envGet("AI_WHISPER_EVALUATOR_OPENAI_MODEL") ?? evalOpenai.model ?? null,
6960
+ baseURL: envGet("AI_WHISPER_EVALUATOR_OPENAI_BASE_URL") ?? evalOpenai.baseURL ?? null
6961
+ },
6962
+ agentCli: {
6963
+ agent,
6964
+ executable: evalAgentCli.executable ?? null,
6965
+ execArgs: Array.isArray(evalAgentCli.execArgs) ? evalAgentCli.execArgs : null,
6966
+ promptVia: evalAgentCli.promptVia === "arg" || evalAgentCli.promptVia === "stdin" ? evalAgentCli.promptVia : null,
6967
+ model: evalAgentCli.model ?? null
6801
6968
  }
6802
6969
  };
6803
6970
  }
@@ -6807,6 +6974,21 @@ function computeEvaluatorStatus(cfg, ctx) {
6807
6974
  if (cfg.provider === "anthropic" && cfg.anthropic.apiKey === null) {
6808
6975
  return "missing_anthropic_key";
6809
6976
  }
6977
+ if (cfg.provider === "openai") {
6978
+ if (cfg.openai.apiKey === null) return "missing_openai_key";
6979
+ if (cfg.openai.model === null) return "invalid_config";
6980
+ }
6981
+ if (cfg.provider === "agent-cli") {
6982
+ if (cfg.agentCli.agent === null) return "invalid_config";
6983
+ const { executable } = resolveAgentCliInvocation({
6984
+ agent: cfg.agentCli.agent,
6985
+ executable: cfg.agentCli.executable,
6986
+ execArgs: cfg.agentCli.execArgs,
6987
+ promptVia: cfg.agentCli.promptVia
6988
+ });
6989
+ const exists = (ctx.executableExists ?? isExecutableOnPath)(executable);
6990
+ if (!exists) return "agent_cli_unavailable";
6991
+ }
6810
6992
  return "ready";
6811
6993
  }
6812
6994
 
@@ -6815,7 +6997,9 @@ var NOT_CONFIGURED = {
6815
6997
  provider: "anthropic",
6816
6998
  fallback: null,
6817
6999
  anthropic: { apiKey: null, model: null },
6818
- ollama: { host: null, model: null }
7000
+ ollama: { host: null, model: null },
7001
+ openai: { apiKey: null, model: null, baseURL: null },
7002
+ agentCli: { agent: null, executable: null, execArgs: null, promptVia: null, model: null }
6819
7003
  };
6820
7004
  function recordEvaluatorStatus(db, input) {
6821
7005
  const status = computeEvaluatorStatus(input.resolved ?? NOT_CONFIGURED, {
@@ -6828,7 +7012,7 @@ function recordEvaluatorStatus(db, input) {
6828
7012
 
6829
7013
  // src/bin/broker-daemon.ts
6830
7014
  import { execFile as execFile2 } from "node:child_process";
6831
- import { join as join7 } from "node:path";
7015
+ import { join as join8 } from "node:path";
6832
7016
 
6833
7017
  // src/runtime/cli-package-info.ts
6834
7018
  import { readFileSync as readFileSync3 } from "node:fs";
@@ -6878,7 +7062,7 @@ if (collabId) {
6878
7062
  });
6879
7063
  workflowEventBridge.start();
6880
7064
  eventSocket = await createEventSocketServer({
6881
- socketPath: join7(getStateSocketsDir(), `events-${collabId}.sock`),
7065
+ socketPath: join8(getStateSocketsDir(), `events-${collabId}.sock`),
6882
7066
  events: [broker.events, externalEvents],
6883
7067
  engineVersion: resolveCliVersion()
6884
7068
  });
@@ -6905,10 +7089,30 @@ function providerConfigFrom(kind) {
6905
7089
  if (!resolved || resolved.anthropic.apiKey === null) return null;
6906
7090
  return { provider: "anthropic", apiKey: resolved.anthropic.apiKey };
6907
7091
  }
7092
+ if (kind === "openai") {
7093
+ if (!resolved || resolved.openai.apiKey === null || resolved.openai.model === null) return null;
7094
+ return {
7095
+ provider: "openai",
7096
+ apiKey: resolved.openai.apiKey,
7097
+ model: resolved.openai.model,
7098
+ ...resolved.openai.baseURL ? { baseURL: resolved.openai.baseURL } : {}
7099
+ };
7100
+ }
7101
+ if (kind === "ollama") {
7102
+ return {
7103
+ provider: "ollama",
7104
+ ...resolved?.ollama.host ? { host: resolved.ollama.host } : {},
7105
+ ...resolved?.ollama.model ? { model: resolved.ollama.model } : {}
7106
+ };
7107
+ }
7108
+ if (!resolved || resolved.agentCli.agent === null) return null;
6908
7109
  return {
6909
- provider: "ollama",
6910
- ...resolved?.ollama.host ? { host: resolved.ollama.host } : {},
6911
- ...resolved?.ollama.model ? { model: resolved.ollama.model } : {}
7110
+ provider: "agent-cli",
7111
+ agent: resolved.agentCli.agent,
7112
+ ...resolved.agentCli.executable ? { executable: resolved.agentCli.executable } : {},
7113
+ ...resolved.agentCli.execArgs ? { execArgs: resolved.agentCli.execArgs } : {},
7114
+ ...resolved.agentCli.promptVia ? { promptVia: resolved.agentCli.promptVia } : {},
7115
+ ...resolved.agentCli.model ? { model: resolved.agentCli.model } : {}
6912
7116
  };
6913
7117
  }
6914
7118
  var collab = broker.control.getCollab(collabId);