counselors 0.4.9 → 0.4.11

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/cli.js CHANGED
@@ -218,7 +218,7 @@ var SAFE_ID_RE = /^[a-zA-Z0-9._-]+$/;
218
218
  function sanitizePath(p) {
219
219
  return p.replace(/[\x00-\x08\x0A-\x1F]/g, "");
220
220
  }
221
- var VERSION = true ? "0.4.9" : "0.0.0-dev";
221
+ var VERSION = true ? "0.4.11" : "0.0.0-dev";
222
222
 
223
223
  // src/types.ts
224
224
  import { z } from "zod";
@@ -397,10 +397,14 @@ async function selectModelDetails(toolId, models) {
397
397
  name: m.recommended ? `${m.name} (Recommended)` : m.name,
398
398
  value: String(i)
399
399
  }));
400
+ choices.push({ name: "Custom model...", value: "__custom__" });
400
401
  const idx = await select({
401
402
  message: `Select model for ${toolId}:`,
402
403
  choices
403
404
  });
405
+ if (idx === "__custom__") {
406
+ return { id: "__custom__" };
407
+ }
404
408
  const model = models[Number(idx)];
405
409
  return {
406
410
  id: model.id,
@@ -615,6 +619,7 @@ import { existsSync as existsSync3 } from "fs";
615
619
 
616
620
  // src/adapters/base.ts
617
621
  var BaseAdapter = class {
622
+ modelFlag = "-m";
618
623
  getEffectiveReadOnlyLevel(_toolConfig) {
619
624
  return this.readOnly.level;
620
625
  }
@@ -713,6 +718,7 @@ var ClaudeAdapter = class extends BaseAdapter {
713
718
  commands = ["claude"];
714
719
  installUrl = "https://docs.anthropic.com/en/docs/claude-code";
715
720
  readOnly = { level: "enforced" };
721
+ modelFlag = "--model";
716
722
  models = [
717
723
  {
718
724
  id: "opus",
@@ -2127,6 +2133,8 @@ var ENV_ALLOWLIST = [
2127
2133
  "GEMINI_API_KEY",
2128
2134
  "GOOGLE_API_KEY",
2129
2135
  "GOOGLE_APPLICATION_CREDENTIALS",
2136
+ "GOOGLE_CLOUD_PROJECT",
2137
+ "GOOGLE_CLOUD_LOCATION",
2130
2138
  "AMP_API_KEY",
2131
2139
  // Proxy
2132
2140
  "HTTP_PROXY",
@@ -2155,7 +2163,7 @@ function normalizeWindowsPathForComparison(path) {
2155
2163
  const withoutTrailing = normalized === root ? normalized : normalized.replace(/[\\/]+$/, "");
2156
2164
  return withoutTrailing.toLowerCase();
2157
2165
  }
2158
- function execute(invocation, timeoutMs) {
2166
+ function execute(invocation, timeoutMs, onSpawn) {
2159
2167
  return new Promise((resolve7) => {
2160
2168
  const start = Date.now();
2161
2169
  let stdout = "";
@@ -2193,6 +2201,7 @@ function execute(invocation, timeoutMs) {
2193
2201
  shell: false,
2194
2202
  windowsHide: true
2195
2203
  });
2204
+ onSpawn?.(child.pid);
2196
2205
  const stdoutStream = child.stdout;
2197
2206
  const stderrStream = child.stderr;
2198
2207
  const stdinStream = child.stdin;
@@ -2314,13 +2323,16 @@ async function executeTest(adapter, toolConfig, toolName) {
2314
2323
  const cmdStr = [invocation.cmd, ...invocation.args].map(quote).join(" ");
2315
2324
  const command = invocation.stdin != null ? `echo ${quote(invocation.stdin)} | ${cmdStr}` : cmdStr;
2316
2325
  const result = await execute(invocation, TEST_TIMEOUT);
2317
- const passed = result.exitCode === 0 && result.stdout.includes("OK");
2326
+ const echoedPrompt = result.stdout.includes("User instructions");
2327
+ const passed = result.exitCode === 0 && result.stdout.includes("OK") && !echoedPrompt;
2318
2328
  let error2;
2319
2329
  if (!passed) {
2320
2330
  if (result.timedOut) {
2321
2331
  error2 = `Timed out after ${TEST_TIMEOUT / 1e3}s`;
2322
2332
  } else if (result.exitCode !== 0) {
2323
2333
  error2 = result.stderr.trim() || `Process exited with code ${result.exitCode}`;
2334
+ } else if (echoedPrompt) {
2335
+ error2 = "Tool echoed the prompt instead of a model response (check model access)";
2324
2336
  } else if (result.stderr.trim()) {
2325
2337
  error2 = result.stderr.slice(0, 500);
2326
2338
  } else {
@@ -2640,8 +2652,9 @@ async function dispatch(options) {
2640
2652
  const isAmp = (toolConfig.adapter ?? id) === "amp";
2641
2653
  const usageBefore = isAmp ? await captureAmpUsage() : null;
2642
2654
  debug(`Dispatching ${id}`);
2643
- onProgress?.({ toolId: id, event: "started" });
2644
- const result = await execute(invocation, toolTimeoutMs);
2655
+ const result = await execute(invocation, toolTimeoutMs, (pid) => {
2656
+ onProgress?.({ toolId: id, event: "started", pid });
2657
+ });
2645
2658
  const usageAfter = isAmp ? await captureAmpUsage() : null;
2646
2659
  const cost = isAmp && usageBefore && usageAfter ? computeAmpCostFromSnapshots(usageBefore, usageAfter) : void 0;
2647
2660
  const safeId = sanitizeId(id);
@@ -2827,6 +2840,7 @@ var ProgressDisplay = class {
2827
2840
  frame = 0;
2828
2841
  lineCount = 0;
2829
2842
  isTTY;
2843
+ infoNotePrinted = false;
2830
2844
  constructor(toolIds, outputDir) {
2831
2845
  this.isTTY = Boolean(process.stderr.isTTY);
2832
2846
  this.outputDir = outputDir;
@@ -2848,15 +2862,22 @@ var ProgressDisplay = class {
2848
2862
  } else {
2849
2863
  process.stderr.write(` Output: ${this.outputDir}
2850
2864
  `);
2865
+ process.stderr.write(` \u2139 This may take more than 10 minutes
2866
+ `);
2867
+ process.stderr.write(` PID: ${process.pid}
2868
+ `);
2869
+ this.infoNotePrinted = true;
2851
2870
  }
2852
2871
  }
2853
- start(toolId) {
2872
+ start(toolId, pid) {
2854
2873
  const tool = this.tools.get(toolId);
2855
2874
  if (!tool) return;
2856
2875
  tool.status = "running";
2857
2876
  tool.startedAt = Date.now();
2877
+ tool.pid = pid;
2858
2878
  if (!this.isTTY) {
2859
- process.stderr.write(` \u25B8 ${toolId} started
2879
+ const pidStr = pid ? `PID ${pid} ` : "";
2880
+ process.stderr.write(` \u25B8 ${pidStr}${toolId} started
2860
2881
  `);
2861
2882
  }
2862
2883
  }
@@ -2892,6 +2913,18 @@ var ProgressDisplay = class {
2892
2913
  render() {
2893
2914
  const lines = [];
2894
2915
  lines.push(` ${DIM}Output: ${this.outputDir}${RESET}`);
2916
+ if (!this.infoNotePrinted) {
2917
+ const anyStarted = this.order.some(
2918
+ (id) => this.tools.get(id).status !== "pending"
2919
+ );
2920
+ if (anyStarted) {
2921
+ this.infoNotePrinted = true;
2922
+ }
2923
+ }
2924
+ if (this.infoNotePrinted) {
2925
+ lines.push(` \u2139 This may take more than 10 minutes`);
2926
+ lines.push(` PID: ${process.pid}`);
2927
+ }
2895
2928
  for (const id of this.order) {
2896
2929
  const tool = this.tools.get(id);
2897
2930
  lines.push(this.formatLine(tool));
@@ -2919,8 +2952,10 @@ var ProgressDisplay = class {
2919
2952
  case "running": {
2920
2953
  const spinner = SPINNER_FRAMES[this.frame % SPINNER_FRAMES.length];
2921
2954
  const elapsed = tool.startedAt ? ((Date.now() - tool.startedAt) / 1e3).toFixed(1) : "0.0";
2922
- const pad = " ".repeat(Math.max(0, 40 - label.length));
2923
- return ` ${spinner} ${label}${pad}running ${elapsed.padStart(6)}s`;
2955
+ const pidPrefix = tool.pid ? `PID ${tool.pid} ` : "";
2956
+ const fullLabel = `${pidPrefix}${label}`;
2957
+ const pad = " ".repeat(Math.max(0, 40 - fullLabel.length));
2958
+ return ` ${spinner} ${fullLabel}${pad}running ${elapsed.padStart(6)}s`;
2924
2959
  }
2925
2960
  case "done": {
2926
2961
  const r = tool.report;
@@ -3155,7 +3190,8 @@ function registerRunCommand(program2) {
3155
3190
  readOnlyPolicy,
3156
3191
  cwd,
3157
3192
  onProgress: (event) => {
3158
- if (event.event === "started") display.start(event.toolId);
3193
+ if (event.event === "started")
3194
+ display.start(event.toolId, event.pid);
3159
3195
  if (event.event === "completed")
3160
3196
  display.complete(event.toolId, event.report);
3161
3197
  }
@@ -3321,6 +3357,8 @@ Use \`timeout: 600000\` (10 minutes). Counselors dispatches to the selected agen
3321
3357
 
3322
3358
  **Important**: Use \`-f\` (file mode) so the prompt is sent as-is without wrapping. Use \`--json\` to get structured output for parsing.
3323
3359
 
3360
+ **Timing**: Sessions commonly take more than 10 minutes. Counselors prints each child process PID alongside the agent name in its progress output (e.g. \`PID 12345 claude\`). If a run seems stuck, you can verify processes are still alive with \`ps -p <PID>\` (macOS/Linux) or \`tasklist /FI "PID eq <PID>"\` (Windows).
3361
+
3324
3362
  ---
3325
3363
 
3326
3364
  ## Phase 5: Read Results
@@ -3435,8 +3473,26 @@ async function addBuiltInTool(toolId, config, nameOverride) {
3435
3473
  return;
3436
3474
  }
3437
3475
  const selectedModel = await selectModelDetails(toolId, adapter.models);
3438
- const fallbackName = selectedModel.id.startsWith(`${toolId}-`) ? selectedModel.id : `${toolId}-${selectedModel.id}`;
3439
- const defaultName = nameOverride ?? selectedModel.compoundId ?? fallbackName;
3476
+ let extraFlags;
3477
+ let defaultName;
3478
+ if (selectedModel.id === "__custom__") {
3479
+ const modelId = await promptInput("Model identifier:");
3480
+ if (!modelId.trim()) {
3481
+ error("No model identifier provided.");
3482
+ process.exitCode = 1;
3483
+ return;
3484
+ }
3485
+ const extraInput = await promptInput(
3486
+ "Extra flags (optional, space-separated):"
3487
+ );
3488
+ const parsedExtra = extraInput.trim() ? extraInput.trim().split(/\s+/) : [];
3489
+ extraFlags = [adapter.modelFlag ?? "-m", modelId.trim(), ...parsedExtra];
3490
+ defaultName = nameOverride ?? `${toolId}-${sanitizeId(modelId.trim())}`;
3491
+ } else {
3492
+ extraFlags = selectedModel.extraFlags;
3493
+ const fallbackName = selectedModel.id.startsWith(`${toolId}-`) ? selectedModel.id : `${toolId}-${selectedModel.id}`;
3494
+ defaultName = nameOverride ?? selectedModel.compoundId ?? fallbackName;
3495
+ }
3440
3496
  let name = nameOverride ?? await promptInput("Tool name:", defaultName);
3441
3497
  if (!SAFE_ID_RE.test(name)) {
3442
3498
  error(
@@ -3467,7 +3523,7 @@ async function addBuiltInTool(toolId, config, nameOverride) {
3467
3523
  binary: discovery.path,
3468
3524
  readOnly: { level: adapter.readOnly.level },
3469
3525
  adapter: toolId,
3470
- ...selectedModel.extraFlags ? { extraFlags: selectedModel.extraFlags } : {}
3526
+ ...extraFlags ? { extraFlags } : {}
3471
3527
  };
3472
3528
  const updated = addToolToConfig(config, name, toolConfig);
3473
3529
  saveConfig(updated);
@@ -3475,6 +3531,17 @@ async function addBuiltInTool(toolId, config, nameOverride) {
3475
3531
  copyAmpSettings();
3476
3532
  }
3477
3533
  success(`Added "${name}" to config.`);
3534
+ if (selectedModel.id === "__custom__") {
3535
+ info("Testing tool configuration...");
3536
+ const testAdapter = resolveAdapter(name, toolConfig);
3537
+ const result = await executeTest(testAdapter, toolConfig, name);
3538
+ info(formatTestResults([result]));
3539
+ if (!result.passed) {
3540
+ warn(
3541
+ "The tool was saved to your config but the test failed. You may need to check your API access or flags."
3542
+ );
3543
+ }
3544
+ }
3478
3545
  }
3479
3546
  async function collectCustomConfig(config, presetId) {
3480
3547
  let binaryPath = null;