llmist 4.0.0 → 5.1.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/dist/cli.cjs CHANGED
@@ -4952,15 +4952,16 @@ var init_agent = __esm({
4952
4952
  });
4953
4953
  } else if (event.type === "llm_call_end") {
4954
4954
  const info = event.event;
4955
+ const usage = info.usage ?? (info.outputTokens ? {
4956
+ inputTokens: info.inputTokens ?? 0,
4957
+ outputTokens: info.outputTokens,
4958
+ totalTokens: (info.inputTokens ?? 0) + info.outputTokens
4959
+ } : void 0);
4955
4960
  void this.hooks?.observers?.onLLMCallComplete?.({
4956
4961
  iteration: info.iteration,
4957
4962
  options: { model: info.model, messages: [] },
4958
4963
  finishReason: info.finishReason ?? null,
4959
- usage: info.outputTokens ? {
4960
- inputTokens: info.inputTokens ?? 0,
4961
- outputTokens: info.outputTokens,
4962
- totalTokens: (info.inputTokens ?? 0) + info.outputTokens
4963
- } : void 0,
4964
+ usage,
4964
4965
  rawResponse: "",
4965
4966
  finalMessage: "",
4966
4967
  logger: this.logger,
@@ -7003,6 +7004,9 @@ var init_gemini = __esm({
7003
7004
  async countTokens(messages, descriptor, _spec) {
7004
7005
  const client = this.client;
7005
7006
  const contents = this.convertMessagesToContents(messages);
7007
+ if (!contents || contents.length === 0) {
7008
+ return 0;
7009
+ }
7006
7010
  try {
7007
7011
  const response = await client.models.countTokens({
7008
7012
  model: descriptor.name,
@@ -9475,13 +9479,24 @@ ${endPrefix}`
9475
9479
  observers: {
9476
9480
  ...hooks?.observers,
9477
9481
  onLLMCallStart: async (context) => {
9482
+ let inputTokens;
9483
+ try {
9484
+ if (this.client) {
9485
+ inputTokens = await this.client.countTokens(
9486
+ context.options.model,
9487
+ context.options.messages
9488
+ );
9489
+ }
9490
+ } catch {
9491
+ }
9478
9492
  onSubagentEvent({
9479
9493
  type: "llm_call_start",
9480
9494
  gadgetInvocationId: invocationId,
9481
9495
  depth,
9482
9496
  event: {
9483
9497
  iteration: context.iteration,
9484
- model: context.options.model
9498
+ model: context.options.model,
9499
+ inputTokens
9485
9500
  }
9486
9501
  });
9487
9502
  if (existingOnLLMCallStart) {
@@ -9496,8 +9511,13 @@ ${endPrefix}`
9496
9511
  event: {
9497
9512
  iteration: context.iteration,
9498
9513
  model: context.options.model,
9514
+ // Backward compat fields
9515
+ inputTokens: context.usage?.inputTokens,
9499
9516
  outputTokens: context.usage?.outputTokens,
9500
- finishReason: context.finishReason
9517
+ finishReason: context.finishReason ?? void 0,
9518
+ // Full usage object with cache details (for first-class display)
9519
+ usage: context.usage
9520
+ // Cost will be calculated by parent if it has model registry
9501
9521
  }
9502
9522
  });
9503
9523
  if (existingOnLLMCallComplete) {
@@ -9896,7 +9916,6 @@ var OPTION_FLAGS = {
9896
9916
  docker: "--docker",
9897
9917
  dockerRo: "--docker-ro",
9898
9918
  noDocker: "--no-docker",
9899
- dockerDev: "--docker-dev",
9900
9919
  // Multimodal input options
9901
9920
  inputImage: "--image <path>",
9902
9921
  inputAudio: "--audio <path>",
@@ -9931,7 +9950,6 @@ var OPTION_DESCRIPTIONS = {
9931
9950
  docker: "Run agent in a Docker sandbox container for security isolation.",
9932
9951
  dockerRo: "Run in Docker with current directory mounted read-only.",
9933
9952
  noDocker: "Disable Docker sandboxing (override config).",
9934
- dockerDev: "Run in Docker dev mode (mount local source instead of npm install).",
9935
9953
  // Image generation descriptions
9936
9954
  imageSize: "Image size/aspect ratio, e.g. '1024x1024', '1:1', '16:9'.",
9937
9955
  imageQuality: "Image quality: 'standard', 'hd', 'low', 'medium', 'high'.",
@@ -9951,7 +9969,7 @@ var import_commander2 = require("commander");
9951
9969
  // package.json
9952
9970
  var package_default = {
9953
9971
  name: "llmist",
9954
- version: "3.1.0",
9972
+ version: "5.1.0",
9955
9973
  description: "TypeScript LLM client with streaming tool execution. Tools fire mid-stream. Built-in function calling works with any model\u2014no structured outputs or native tool support required.",
9956
9974
  type: "module",
9957
9975
  main: "dist/index.cjs",
@@ -9994,7 +10012,8 @@ var package_default = {
9994
10012
  "test:all": "bun run test && bun run test:e2e",
9995
10013
  clean: "rimraf dist",
9996
10014
  prepare: "node scripts/install-hooks.js || true",
9997
- "release:dry": "bunx semantic-release --dry-run"
10015
+ "release:dry": "bunx semantic-release --dry-run",
10016
+ "release:publish": 'test "$(git branch --show-current)" = "main" && git pull origin main && bun run build && npm publish'
9998
10017
  },
9999
10018
  bin: {
10000
10019
  llmist: "dist/cli.js"
@@ -11353,20 +11372,17 @@ var DOCKER_CONFIG_KEYS = /* @__PURE__ */ new Set([
11353
11372
  "mounts",
11354
11373
  "env-vars",
11355
11374
  "image-name",
11356
- "dev-mode",
11357
- "dev-source",
11358
11375
  "docker-args"
11359
11376
  ]);
11360
11377
  var DEFAULT_IMAGE_NAME = "llmist-sandbox";
11361
11378
  var DEFAULT_CWD_PERMISSION = "rw";
11362
11379
  var DEFAULT_CONFIG_PERMISSION = "ro";
11380
+ var GADGET_CACHE_VOLUME = "llmist-gadget-cache";
11363
11381
  var FORWARDED_API_KEYS = [
11364
11382
  "ANTHROPIC_API_KEY",
11365
11383
  "OPENAI_API_KEY",
11366
11384
  "GEMINI_API_KEY"
11367
11385
  ];
11368
- var DEV_IMAGE_NAME = "llmist-dev-sandbox";
11369
- var DEV_SOURCE_MOUNT_TARGET = "/llmist-src";
11370
11386
 
11371
11387
  // src/cli/docker/docker-config.ts
11372
11388
  var MOUNT_CONFIG_KEYS = /* @__PURE__ */ new Set(["source", "target", "permission"]);
@@ -11478,12 +11494,6 @@ function validateDockerConfig(raw, section) {
11478
11494
  if ("image-name" in rawObj) {
11479
11495
  result["image-name"] = validateString2(rawObj["image-name"], "image-name", section);
11480
11496
  }
11481
- if ("dev-mode" in rawObj) {
11482
- result["dev-mode"] = validateBoolean2(rawObj["dev-mode"], "dev-mode", section);
11483
- }
11484
- if ("dev-source" in rawObj) {
11485
- result["dev-source"] = validateString2(rawObj["dev-source"], "dev-source", section);
11486
- }
11487
11497
  if ("docker-args" in rawObj) {
11488
11498
  result["docker-args"] = validateStringArray2(rawObj["docker-args"], "docker-args", section);
11489
11499
  }
@@ -11493,7 +11503,6 @@ function validateDockerConfig(raw, section) {
11493
11503
  // src/cli/docker/docker-wrapper.ts
11494
11504
  var import_node_fs5 = require("fs");
11495
11505
  var import_node_os4 = require("os");
11496
- var import_node_path6 = require("path");
11497
11506
 
11498
11507
  // src/cli/docker/dockerfile.ts
11499
11508
  var DEFAULT_DOCKERFILE = `# llmist sandbox image
@@ -11513,6 +11522,10 @@ RUN apt-get update && apt-get install -y --no-install-recommends \\
11513
11522
  curl \\
11514
11523
  # ca-certificates for HTTPS
11515
11524
  ca-certificates \\
11525
+ # python3 for native module compilation (node-gyp)
11526
+ python3 \\
11527
+ # build-essential for compiling native modules
11528
+ build-essential \\
11516
11529
  && rm -rf /var/lib/apt/lists/*
11517
11530
 
11518
11531
  # Install ast-grep for code search/refactoring
@@ -11530,37 +11543,8 @@ WORKDIR /workspace
11530
11543
  # Entry point - llmist with all arguments forwarded
11531
11544
  ENTRYPOINT ["llmist"]
11532
11545
  `;
11533
- var DEV_DOCKERFILE = `# llmist DEV sandbox image
11534
- # For development/testing with local source code
11535
-
11536
- FROM oven/bun:1-debian
11537
-
11538
- # Install essential tools (same as production)
11539
- RUN apt-get update && apt-get install -y --no-install-recommends \\
11540
- ed \\
11541
- ripgrep \\
11542
- git \\
11543
- curl \\
11544
- ca-certificates \\
11545
- && rm -rf /var/lib/apt/lists/*
11546
-
11547
- # Install ast-grep for code search/refactoring
11548
- RUN curl -fsSL https://raw.githubusercontent.com/ast-grep/ast-grep/main/install.sh | bash \\
11549
- && mv /root/.local/bin/ast-grep /usr/local/bin/ 2>/dev/null || true \\
11550
- && mv /root/.local/bin/sg /usr/local/bin/ 2>/dev/null || true
11551
-
11552
- # Working directory (host CWD will be mounted here)
11553
- WORKDIR /workspace
11554
-
11555
- # Entry point - run llmist from mounted source
11556
- # Source is mounted at ${DEV_SOURCE_MOUNT_TARGET}
11557
- ENTRYPOINT ["bun", "run", "${DEV_SOURCE_MOUNT_TARGET}/src/cli.ts"]
11558
- `;
11559
- function resolveDockerfile(config, devMode = false) {
11560
- if (config.dockerfile) {
11561
- return config.dockerfile;
11562
- }
11563
- return devMode ? DEV_DOCKERFILE : DEFAULT_DOCKERFILE;
11546
+ function resolveDockerfile(config) {
11547
+ return config.dockerfile ?? DEFAULT_DOCKERFILE;
11564
11548
  }
11565
11549
  function computeDockerfileHash(dockerfile) {
11566
11550
  const encoder = new TextEncoder();
@@ -11622,10 +11606,13 @@ async function buildImage(imageName, dockerfile) {
11622
11606
  ensureCacheDir();
11623
11607
  const dockerfilePath = (0, import_node_path5.join)(CACHE_DIR, "Dockerfile");
11624
11608
  (0, import_node_fs4.writeFileSync)(dockerfilePath, dockerfile);
11625
- const proc = Bun.spawn(["docker", "build", "-t", imageName, "-f", dockerfilePath, CACHE_DIR], {
11626
- stdout: "pipe",
11627
- stderr: "pipe"
11628
- });
11609
+ const proc = Bun.spawn(
11610
+ ["docker", "build", "--no-cache", "-t", imageName, "-f", dockerfilePath, CACHE_DIR],
11611
+ {
11612
+ stdout: "pipe",
11613
+ stderr: "pipe"
11614
+ }
11615
+ );
11629
11616
  const exitCode = await proc.exited;
11630
11617
  const stdout = await new Response(proc.stdout).text();
11631
11618
  const stderr = await new Response(proc.stderr).text();
@@ -11687,46 +11674,13 @@ function isInsideContainer() {
11687
11674
  }
11688
11675
  return false;
11689
11676
  }
11690
- function autoDetectDevSource() {
11691
- const scriptPath = process.argv[1];
11692
- if (!scriptPath || !scriptPath.endsWith("src/cli.ts")) {
11693
- return void 0;
11694
- }
11695
- const srcDir = (0, import_node_path6.dirname)(scriptPath);
11696
- const projectDir = (0, import_node_path6.dirname)(srcDir);
11697
- const packageJsonPath = (0, import_node_path6.join)(projectDir, "package.json");
11698
- if (!(0, import_node_fs5.existsSync)(packageJsonPath)) {
11699
- return void 0;
11700
- }
11701
- try {
11702
- const pkg = JSON.parse((0, import_node_fs5.readFileSync)(packageJsonPath, "utf-8"));
11703
- if (pkg.name === "llmist") {
11704
- return projectDir;
11705
- }
11706
- } catch {
11707
- }
11708
- return void 0;
11709
- }
11710
- function resolveDevMode(config, cliDevMode) {
11711
- const enabled = cliDevMode || config?.["dev-mode"] || process.env.LLMIST_DEV_MODE === "1";
11712
- if (!enabled) {
11713
- return { enabled: false, sourcePath: void 0 };
11714
- }
11715
- const sourcePath = config?.["dev-source"] || process.env.LLMIST_DEV_SOURCE || autoDetectDevSource();
11716
- if (!sourcePath) {
11717
- throw new Error(
11718
- "Docker dev mode enabled but llmist source path not found. Set [docker].dev-source in config, LLMIST_DEV_SOURCE env var, or run from the llmist source directory (bun src/cli.ts)."
11719
- );
11720
- }
11721
- return { enabled: true, sourcePath };
11722
- }
11723
11677
  function expandHome(path6) {
11724
11678
  if (path6.startsWith("~")) {
11725
11679
  return path6.replace(/^~/, (0, import_node_os4.homedir)());
11726
11680
  }
11727
11681
  return path6;
11728
11682
  }
11729
- function buildDockerRunArgs(ctx, imageName, devMode) {
11683
+ function buildDockerRunArgs(ctx, imageName) {
11730
11684
  const args = ["run", "--rm"];
11731
11685
  const timestamp = Date.now();
11732
11686
  const random = Math.random().toString(36).slice(2, 8);
@@ -11740,11 +11694,15 @@ function buildDockerRunArgs(ctx, imageName, devMode) {
11740
11694
  args.push("-w", "/workspace");
11741
11695
  const configPermission = ctx.config["config-permission"] ?? DEFAULT_CONFIG_PERMISSION;
11742
11696
  const llmistDir = expandHome("~/.llmist");
11743
- args.push("-v", `${llmistDir}:/root/.llmist:${configPermission}`);
11744
- if (devMode.enabled && devMode.sourcePath) {
11745
- const expandedSource = expandHome(devMode.sourcePath);
11746
- args.push("-v", `${expandedSource}:${DEV_SOURCE_MOUNT_TARGET}:ro`);
11697
+ const cliTomlPath = `${llmistDir}/cli.toml`;
11698
+ if ((0, import_node_fs5.existsSync)(cliTomlPath)) {
11699
+ args.push("-v", `${cliTomlPath}:/root/.llmist/cli.toml:${configPermission}`);
11700
+ }
11701
+ const gadgetsDir = `${llmistDir}/gadgets`;
11702
+ if ((0, import_node_fs5.existsSync)(gadgetsDir)) {
11703
+ args.push("-v", `${gadgetsDir}:/root/.llmist/gadgets:${configPermission}`);
11747
11704
  }
11705
+ args.push("-v", `${GADGET_CACHE_VOLUME}:/root/.llmist/gadget-cache`);
11748
11706
  if (ctx.config.mounts) {
11749
11707
  for (const mount of ctx.config.mounts) {
11750
11708
  const source = expandHome(mount.source);
@@ -11771,7 +11729,7 @@ function buildDockerRunArgs(ctx, imageName, devMode) {
11771
11729
  return args;
11772
11730
  }
11773
11731
  function filterDockerArgs(argv) {
11774
- const dockerFlags = /* @__PURE__ */ new Set(["--docker", "--docker-ro", "--no-docker", "--docker-dev"]);
11732
+ const dockerFlags = /* @__PURE__ */ new Set(["--docker", "--docker-ro", "--no-docker"]);
11775
11733
  return argv.filter((arg) => !dockerFlags.has(arg));
11776
11734
  }
11777
11735
  function resolveDockerEnabled(config, options, profileDocker) {
@@ -11786,22 +11744,16 @@ function resolveDockerEnabled(config, options, profileDocker) {
11786
11744
  }
11787
11745
  return config?.enabled ?? false;
11788
11746
  }
11789
- async function executeInDocker(ctx, devMode) {
11747
+ async function executeInDocker(ctx) {
11790
11748
  if (isInsideContainer()) {
11791
- console.error(
11792
- "Warning: Docker mode requested but already inside a container. Proceeding without re-containerization."
11793
- );
11794
11749
  throw new DockerSkipError();
11795
11750
  }
11796
11751
  const available = await checkDockerAvailable();
11797
11752
  if (!available) {
11798
11753
  throw new DockerUnavailableError();
11799
11754
  }
11800
- const dockerfile = resolveDockerfile(ctx.config, devMode.enabled);
11801
- const imageName = devMode.enabled ? DEV_IMAGE_NAME : ctx.config["image-name"] ?? DEFAULT_IMAGE_NAME;
11802
- if (devMode.enabled) {
11803
- console.error(`[dev mode] Mounting source from ${devMode.sourcePath}`);
11804
- }
11755
+ const dockerfile = resolveDockerfile(ctx.config);
11756
+ const imageName = ctx.config["image-name"] ?? DEFAULT_IMAGE_NAME;
11805
11757
  try {
11806
11758
  await ensureImage(imageName, dockerfile);
11807
11759
  } catch (error) {
@@ -11812,7 +11764,7 @@ async function executeInDocker(ctx, devMode) {
11812
11764
  }
11813
11765
  throw error;
11814
11766
  }
11815
- const dockerArgs = buildDockerRunArgs(ctx, imageName, devMode);
11767
+ const dockerArgs = buildDockerRunArgs(ctx, imageName);
11816
11768
  const proc = Bun.spawn(["docker", ...dockerArgs], {
11817
11769
  stdin: "inherit",
11818
11770
  stdout: "inherit",
@@ -11833,7 +11785,7 @@ function createDockerContext(config, options, argv, cwd, profileCwdPermission) {
11833
11785
 
11834
11786
  // src/cli/file-utils.ts
11835
11787
  var import_promises3 = require("fs/promises");
11836
- var import_node_path7 = require("path");
11788
+ var import_node_path6 = require("path");
11837
11789
  init_input_content();
11838
11790
  var DEFAULT_MAX_FILE_SIZE = 50 * 1024 * 1024;
11839
11791
  function formatFileSize(bytes) {
@@ -11851,7 +11803,7 @@ async function checkFileSize(absolutePath, filePath, maxSize) {
11851
11803
  }
11852
11804
  }
11853
11805
  async function readImageFile(filePath, options = {}) {
11854
- const absolutePath = (0, import_node_path7.resolve)(filePath);
11806
+ const absolutePath = (0, import_node_path6.resolve)(filePath);
11855
11807
  const maxFileSize = options.maxFileSize ?? DEFAULT_MAX_FILE_SIZE;
11856
11808
  let buffer;
11857
11809
  try {
@@ -11870,7 +11822,7 @@ async function readImageFile(filePath, options = {}) {
11870
11822
  return imageFromBuffer(buffer, mimeType);
11871
11823
  }
11872
11824
  async function readAudioFile(filePath, options = {}) {
11873
- const absolutePath = (0, import_node_path7.resolve)(filePath);
11825
+ const absolutePath = (0, import_node_path6.resolve)(filePath);
11874
11826
  const maxFileSize = options.maxFileSize ?? DEFAULT_MAX_FILE_SIZE;
11875
11827
  let buffer;
11876
11828
  try {
@@ -11889,7 +11841,7 @@ async function readAudioFile(filePath, options = {}) {
11889
11841
  return audioFromBuffer(buffer, mimeType);
11890
11842
  }
11891
11843
  async function readFileBuffer(filePath, options = {}) {
11892
- const absolutePath = (0, import_node_path7.resolve)(filePath);
11844
+ const absolutePath = (0, import_node_path6.resolve)(filePath);
11893
11845
  const maxFileSize = options.maxFileSize ?? DEFAULT_MAX_FILE_SIZE;
11894
11846
  try {
11895
11847
  await checkFileSize(absolutePath, filePath, maxFileSize);
@@ -11902,7 +11854,7 @@ async function readFileBuffer(filePath, options = {}) {
11902
11854
 
11903
11855
  // src/cli/gadgets.ts
11904
11856
  var import_node_fs11 = __toESM(require("fs"), 1);
11905
- var import_node_path12 = __toESM(require("path"), 1);
11857
+ var import_node_path11 = __toESM(require("path"), 1);
11906
11858
  var import_node_url2 = require("url");
11907
11859
  init_gadget();
11908
11860
 
@@ -11982,7 +11934,7 @@ init_gadget();
11982
11934
 
11983
11935
  // src/cli/builtins/filesystem/utils.ts
11984
11936
  var import_node_fs6 = __toESM(require("fs"), 1);
11985
- var import_node_path8 = __toESM(require("path"), 1);
11937
+ var import_node_path7 = __toESM(require("path"), 1);
11986
11938
  var PathSandboxException = class extends Error {
11987
11939
  constructor(inputPath, reason) {
11988
11940
  super(`Path access denied: ${inputPath}. ${reason}`);
@@ -11991,7 +11943,7 @@ var PathSandboxException = class extends Error {
11991
11943
  };
11992
11944
  function validatePathIsWithinCwd(inputPath) {
11993
11945
  const cwd = process.cwd();
11994
- const resolvedPath = import_node_path8.default.resolve(cwd, inputPath);
11946
+ const resolvedPath = import_node_path7.default.resolve(cwd, inputPath);
11995
11947
  let finalPath;
11996
11948
  try {
11997
11949
  finalPath = import_node_fs6.default.realpathSync(resolvedPath);
@@ -12003,7 +11955,7 @@ function validatePathIsWithinCwd(inputPath) {
12003
11955
  throw error;
12004
11956
  }
12005
11957
  }
12006
- const cwdWithSep = cwd + import_node_path8.default.sep;
11958
+ const cwdWithSep = cwd + import_node_path7.default.sep;
12007
11959
  if (!finalPath.startsWith(cwdWithSep) && finalPath !== cwd) {
12008
11960
  throw new PathSandboxException(inputPath, "Path is outside the current working directory");
12009
11961
  }
@@ -12106,15 +12058,15 @@ error: ${message}`;
12106
12058
 
12107
12059
  // src/cli/builtins/filesystem/list-directory.ts
12108
12060
  var import_node_fs7 = __toESM(require("fs"), 1);
12109
- var import_node_path9 = __toESM(require("path"), 1);
12061
+ var import_node_path8 = __toESM(require("path"), 1);
12110
12062
  var import_zod5 = require("zod");
12111
12063
  function listFiles(dirPath, basePath = dirPath, maxDepth = 1, currentDepth = 1) {
12112
12064
  const entries = [];
12113
12065
  try {
12114
12066
  const items = import_node_fs7.default.readdirSync(dirPath);
12115
12067
  for (const item of items) {
12116
- const fullPath = import_node_path9.default.join(dirPath, item);
12117
- const relativePath = import_node_path9.default.relative(basePath, fullPath);
12068
+ const fullPath = import_node_path8.default.join(dirPath, item);
12069
+ const relativePath = import_node_path8.default.relative(basePath, fullPath);
12118
12070
  try {
12119
12071
  const stats = import_node_fs7.default.lstatSync(fullPath);
12120
12072
  let type;
@@ -12258,7 +12210,7 @@ ${content}`;
12258
12210
 
12259
12211
  // src/cli/builtins/filesystem/write-file.ts
12260
12212
  var import_node_fs9 = __toESM(require("fs"), 1);
12261
- var import_node_path10 = __toESM(require("path"), 1);
12213
+ var import_node_path9 = __toESM(require("path"), 1);
12262
12214
  var import_zod7 = require("zod");
12263
12215
  var writeFile2 = createGadget({
12264
12216
  name: "WriteFile",
@@ -12293,7 +12245,7 @@ console.log(\`Server running on http://localhost:\${port}\`);`
12293
12245
  ],
12294
12246
  execute: ({ filePath, content }) => {
12295
12247
  const validatedPath = validatePathIsWithinCwd(filePath);
12296
- const parentDir = import_node_path10.default.dirname(validatedPath);
12248
+ const parentDir = import_node_path9.default.dirname(validatedPath);
12297
12249
  let createdDir = false;
12298
12250
  if (!import_node_fs9.default.existsSync(parentDir)) {
12299
12251
  validatePathIsWithinCwd(parentDir);
@@ -12302,7 +12254,7 @@ console.log(\`Server running on http://localhost:\${port}\`);`
12302
12254
  }
12303
12255
  import_node_fs9.default.writeFileSync(validatedPath, content, "utf-8");
12304
12256
  const bytesWritten = Buffer.byteLength(content, "utf-8");
12305
- const dirNote = createdDir ? ` (created directory: ${import_node_path10.default.dirname(filePath)})` : "";
12257
+ const dirNote = createdDir ? ` (created directory: ${import_node_path9.default.dirname(filePath)})` : "";
12306
12258
  return `path=${filePath}
12307
12259
 
12308
12260
  Wrote ${bytesWritten} bytes${dirNote}`;
@@ -12431,10 +12383,10 @@ function isBuiltinGadgetName(name) {
12431
12383
  // src/cli/external-gadgets.ts
12432
12384
  var import_node_child_process = require("child_process");
12433
12385
  var import_node_fs10 = __toESM(require("fs"), 1);
12434
- var import_node_path11 = __toESM(require("path"), 1);
12386
+ var import_node_path10 = __toESM(require("path"), 1);
12435
12387
  var import_node_os5 = __toESM(require("os"), 1);
12436
12388
  var import_node_url = require("url");
12437
- var CACHE_DIR2 = import_node_path11.default.join(import_node_os5.default.homedir(), ".llmist", "gadget-cache");
12389
+ var CACHE_DIR2 = import_node_path10.default.join(import_node_os5.default.homedir(), ".llmist", "gadget-cache");
12438
12390
  function isExternalPackageSpecifier(specifier) {
12439
12391
  if (/^@?[a-z0-9][\w.-]*(?:@[\w.-]+)?(?::[a-z]+)?(?:\/\w+)?$/i.test(specifier)) {
12440
12392
  return true;
@@ -12472,13 +12424,13 @@ function parseGadgetSpecifier(specifier) {
12472
12424
  function getCacheDir(spec) {
12473
12425
  const versionSuffix = spec.version ? `@${spec.version}` : "@latest";
12474
12426
  if (spec.type === "npm") {
12475
- return import_node_path11.default.join(CACHE_DIR2, "npm", `${spec.package}${versionSuffix}`);
12427
+ return import_node_path10.default.join(CACHE_DIR2, "npm", `${spec.package}${versionSuffix}`);
12476
12428
  }
12477
12429
  const sanitizedUrl = spec.package.replace(/[/:]/g, "-").replace(/^-+|-+$/g, "");
12478
- return import_node_path11.default.join(CACHE_DIR2, "git", `${sanitizedUrl}${versionSuffix}`);
12430
+ return import_node_path10.default.join(CACHE_DIR2, "git", `${sanitizedUrl}${versionSuffix}`);
12479
12431
  }
12480
12432
  function isCached(cacheDir) {
12481
- const packageJsonPath = import_node_path11.default.join(cacheDir, "package.json");
12433
+ const packageJsonPath = import_node_path10.default.join(cacheDir, "package.json");
12482
12434
  return import_node_fs10.default.existsSync(packageJsonPath);
12483
12435
  }
12484
12436
  async function installNpmPackage(spec, cacheDir) {
@@ -12488,10 +12440,10 @@ async function installNpmPackage(spec, cacheDir) {
12488
12440
  private: true,
12489
12441
  type: "module"
12490
12442
  };
12491
- import_node_fs10.default.writeFileSync(import_node_path11.default.join(cacheDir, "package.json"), JSON.stringify(packageJson, null, 2));
12443
+ import_node_fs10.default.writeFileSync(import_node_path10.default.join(cacheDir, "package.json"), JSON.stringify(packageJson, null, 2));
12492
12444
  const packageSpec = spec.version ? `${spec.package}@${spec.version}` : spec.package;
12493
12445
  try {
12494
- (0, import_node_child_process.execSync)(`npm install --prefix "${cacheDir}" "${packageSpec}" --save`, {
12446
+ (0, import_node_child_process.execSync)(`bun add "${packageSpec}"`, {
12495
12447
  stdio: "pipe",
12496
12448
  cwd: cacheDir
12497
12449
  });
@@ -12501,7 +12453,7 @@ async function installNpmPackage(spec, cacheDir) {
12501
12453
  }
12502
12454
  }
12503
12455
  async function installGitPackage(spec, cacheDir) {
12504
- import_node_fs10.default.mkdirSync(import_node_path11.default.dirname(cacheDir), { recursive: true });
12456
+ import_node_fs10.default.mkdirSync(import_node_path10.default.dirname(cacheDir), { recursive: true });
12505
12457
  if (import_node_fs10.default.existsSync(cacheDir)) {
12506
12458
  try {
12507
12459
  (0, import_node_child_process.execSync)("git fetch", { cwd: cacheDir, stdio: "pipe" });
@@ -12520,17 +12472,17 @@ async function installGitPackage(spec, cacheDir) {
12520
12472
  const message = error instanceof Error ? error.message : String(error);
12521
12473
  throw new Error(`Failed to clone git repository '${spec.package}': ${message}`);
12522
12474
  }
12523
- if (import_node_fs10.default.existsSync(import_node_path11.default.join(cacheDir, "package.json"))) {
12475
+ if (import_node_fs10.default.existsSync(import_node_path10.default.join(cacheDir, "package.json"))) {
12524
12476
  try {
12525
- (0, import_node_child_process.execSync)("npm install", { cwd: cacheDir, stdio: "pipe" });
12477
+ (0, import_node_child_process.execSync)("bun install", { cwd: cacheDir, stdio: "inherit" });
12526
12478
  } catch (error) {
12527
12479
  const message = error instanceof Error ? error.message : String(error);
12528
12480
  throw new Error(`Failed to install dependencies for '${spec.package}': ${message}`);
12529
12481
  }
12530
12482
  try {
12531
- const packageJson = JSON.parse(import_node_fs10.default.readFileSync(import_node_path11.default.join(cacheDir, "package.json"), "utf-8"));
12483
+ const packageJson = JSON.parse(import_node_fs10.default.readFileSync(import_node_path10.default.join(cacheDir, "package.json"), "utf-8"));
12532
12484
  if (packageJson.scripts?.build) {
12533
- (0, import_node_child_process.execSync)("npm run build", { cwd: cacheDir, stdio: "pipe" });
12485
+ (0, import_node_child_process.execSync)("bun run build", { cwd: cacheDir, stdio: "inherit" });
12534
12486
  }
12535
12487
  } catch (error) {
12536
12488
  const message = error instanceof Error ? error.message : String(error);
@@ -12540,7 +12492,7 @@ async function installGitPackage(spec, cacheDir) {
12540
12492
  }
12541
12493
  }
12542
12494
  function readManifest(packageDir) {
12543
- const packageJsonPath = import_node_path11.default.join(packageDir, "package.json");
12495
+ const packageJsonPath = import_node_path10.default.join(packageDir, "package.json");
12544
12496
  if (!import_node_fs10.default.existsSync(packageJsonPath)) {
12545
12497
  return null;
12546
12498
  }
@@ -12552,7 +12504,7 @@ function readManifest(packageDir) {
12552
12504
  }
12553
12505
  }
12554
12506
  function getPackagePath(cacheDir, packageName) {
12555
- const nodeModulesPath = import_node_path11.default.join(cacheDir, "node_modules", packageName);
12507
+ const nodeModulesPath = import_node_path10.default.join(cacheDir, "node_modules", packageName);
12556
12508
  if (import_node_fs10.default.existsSync(nodeModulesPath)) {
12557
12509
  return nodeModulesPath;
12558
12510
  }
@@ -12596,7 +12548,7 @@ async function loadExternalGadgets(specifier, forceInstall = false) {
12596
12548
  } else {
12597
12549
  entryPoint = manifest?.gadgets || "./dist/index.js";
12598
12550
  }
12599
- const resolvedEntryPoint = import_node_path11.default.resolve(packagePath, entryPoint);
12551
+ const resolvedEntryPoint = import_node_path10.default.resolve(packagePath, entryPoint);
12600
12552
  if (!import_node_fs10.default.existsSync(resolvedEntryPoint)) {
12601
12553
  throw new Error(
12602
12554
  `Entry point not found: ${resolvedEntryPoint}. Make sure the package is built (run 'npm run build' in the package directory).`
@@ -12665,10 +12617,10 @@ function expandHomePath(input) {
12665
12617
  if (!home) {
12666
12618
  return input;
12667
12619
  }
12668
- return import_node_path12.default.join(home, input.slice(1));
12620
+ return import_node_path11.default.join(home, input.slice(1));
12669
12621
  }
12670
12622
  function isFileLikeSpecifier(specifier) {
12671
- return PATH_PREFIXES.some((prefix) => specifier.startsWith(prefix)) || specifier.includes(import_node_path12.default.sep);
12623
+ return PATH_PREFIXES.some((prefix) => specifier.startsWith(prefix)) || specifier.includes(import_node_path11.default.sep);
12672
12624
  }
12673
12625
  function tryResolveBuiltin(specifier) {
12674
12626
  if (specifier.startsWith(BUILTIN_PREFIX)) {
@@ -12691,7 +12643,7 @@ function resolveGadgetSpecifier(specifier, cwd) {
12691
12643
  return specifier;
12692
12644
  }
12693
12645
  const expanded = expandHomePath(specifier);
12694
- const resolvedPath = import_node_path12.default.resolve(cwd, expanded);
12646
+ const resolvedPath = import_node_path11.default.resolve(cwd, expanded);
12695
12647
  if (!import_node_fs11.default.existsSync(resolvedPath)) {
12696
12648
  throw new Error(`Gadget module not found at ${resolvedPath}`);
12697
12649
  }
@@ -12776,12 +12728,12 @@ async function loadGadgets(specifiers, cwd, importer = (specifier) => import(spe
12776
12728
  // src/cli/llm-logging.ts
12777
12729
  var import_promises4 = require("fs/promises");
12778
12730
  var import_node_os6 = require("os");
12779
- var import_node_path13 = require("path");
12731
+ var import_node_path12 = require("path");
12780
12732
  init_messages();
12781
- var DEFAULT_LLM_LOG_DIR = (0, import_node_path13.join)((0, import_node_os6.homedir)(), ".llmist", "logs");
12733
+ var DEFAULT_LLM_LOG_DIR = (0, import_node_path12.join)((0, import_node_os6.homedir)(), ".llmist", "logs");
12782
12734
  function resolveLogDir(option, subdir) {
12783
12735
  if (option === true) {
12784
- return (0, import_node_path13.join)(DEFAULT_LLM_LOG_DIR, subdir);
12736
+ return (0, import_node_path12.join)(DEFAULT_LLM_LOG_DIR, subdir);
12785
12737
  }
12786
12738
  if (typeof option === "string") {
12787
12739
  return option;
@@ -12799,7 +12751,7 @@ function formatLlmRequest(messages) {
12799
12751
  }
12800
12752
  async function writeLogFile(dir, filename, content) {
12801
12753
  await (0, import_promises4.mkdir)(dir, { recursive: true });
12802
- await (0, import_promises4.writeFile)((0, import_node_path13.join)(dir, filename), content, "utf-8");
12754
+ await (0, import_promises4.writeFile)((0, import_node_path12.join)(dir, filename), content, "utf-8");
12803
12755
  }
12804
12756
  function formatSessionTimestamp(date = /* @__PURE__ */ new Date()) {
12805
12757
  const pad = (n) => n.toString().padStart(2, "0");
@@ -12813,7 +12765,7 @@ function formatSessionTimestamp(date = /* @__PURE__ */ new Date()) {
12813
12765
  }
12814
12766
  async function createSessionDir(baseDir) {
12815
12767
  const timestamp = formatSessionTimestamp();
12816
- const sessionDir = (0, import_node_path13.join)(baseDir, timestamp);
12768
+ const sessionDir = (0, import_node_path12.join)(baseDir, timestamp);
12817
12769
  try {
12818
12770
  await (0, import_promises4.mkdir)(sessionDir, { recursive: true });
12819
12771
  return sessionDir;
@@ -12906,6 +12858,45 @@ function formatCost(cost) {
12906
12858
  }
12907
12859
  return cost.toFixed(2);
12908
12860
  }
12861
+ function formatLLMCallLine(info) {
12862
+ const parts = [];
12863
+ parts.push(`${import_chalk3.default.cyan(`#${info.iteration}`)} ${import_chalk3.default.magenta(info.model)}`);
12864
+ if (info.contextPercent !== void 0 && info.contextPercent !== null) {
12865
+ const formatted = `${Math.round(info.contextPercent)}%`;
12866
+ if (info.contextPercent >= 80) {
12867
+ parts.push(import_chalk3.default.red(formatted));
12868
+ } else if (info.contextPercent >= 50) {
12869
+ parts.push(import_chalk3.default.yellow(formatted));
12870
+ } else {
12871
+ parts.push(import_chalk3.default.green(formatted));
12872
+ }
12873
+ }
12874
+ if (info.inputTokens && info.inputTokens > 0) {
12875
+ const prefix = info.estimated?.input ? "~" : "";
12876
+ parts.push(import_chalk3.default.dim("\u2191") + import_chalk3.default.yellow(` ${prefix}${formatTokens(info.inputTokens)}`));
12877
+ }
12878
+ if (info.cachedInputTokens && info.cachedInputTokens > 0) {
12879
+ parts.push(import_chalk3.default.dim("\u27F3") + import_chalk3.default.blue(` ${formatTokens(info.cachedInputTokens)}`));
12880
+ }
12881
+ if (info.outputTokens !== void 0 && info.outputTokens > 0 || info.isStreaming) {
12882
+ const prefix = info.estimated?.output ? "~" : "";
12883
+ parts.push(import_chalk3.default.dim("\u2193") + import_chalk3.default.green(` ${prefix}${formatTokens(info.outputTokens ?? 0)}`));
12884
+ }
12885
+ parts.push(import_chalk3.default.dim(`${info.elapsedSeconds.toFixed(1)}s`));
12886
+ if (info.cost !== void 0 && info.cost > 0) {
12887
+ parts.push(import_chalk3.default.cyan(`$${formatCost(info.cost)}`));
12888
+ }
12889
+ if (info.isStreaming && info.spinner) {
12890
+ parts.push(import_chalk3.default.cyan(info.spinner));
12891
+ } else if (info.finishReason !== void 0) {
12892
+ if (!info.finishReason || info.finishReason === "stop" || info.finishReason === "end_turn") {
12893
+ parts.push(import_chalk3.default.green("\u2713"));
12894
+ } else {
12895
+ parts.push(import_chalk3.default.yellow(info.finishReason));
12896
+ }
12897
+ }
12898
+ return parts.join(import_chalk3.default.dim(" | "));
12899
+ }
12909
12900
  function renderSummary(metadata) {
12910
12901
  const parts = [];
12911
12902
  if (metadata.iterations !== void 0) {
@@ -12974,7 +12965,7 @@ function getRawValue(value) {
12974
12965
  function truncateValue(str, maxLen) {
12975
12966
  if (maxLen <= 0) return "";
12976
12967
  if (str.length <= maxLen) return str;
12977
- return `${str.slice(0, maxLen)}\u2026`;
12968
+ return `${str.slice(0, maxLen - 1)}\u2026`;
12978
12969
  }
12979
12970
  function formatParametersInline(params, maxWidth) {
12980
12971
  if (!params || Object.keys(params).length === 0) {
@@ -13002,6 +12993,11 @@ function formatParametersInline(params, maxWidth) {
13002
12993
  const proportion = v.length / totalRawLength;
13003
12994
  return Math.max(minPerValue, Math.floor(proportion * availableForValues));
13004
12995
  });
12996
+ const totalLimits = limits.reduce((sum, l) => sum + l, 0);
12997
+ if (totalLimits > availableForValues) {
12998
+ const scale = availableForValues / totalLimits;
12999
+ limits = limits.map((l) => Math.max(1, Math.floor(l * scale)));
13000
+ }
13005
13001
  }
13006
13002
  }
13007
13003
  } else {
@@ -13012,6 +13008,38 @@ function formatParametersInline(params, maxWidth) {
13012
13008
  return `${import_chalk3.default.dim(key)}${import_chalk3.default.dim("=")}${import_chalk3.default.cyan(formatted)}`;
13013
13009
  }).join(import_chalk3.default.dim(", "));
13014
13010
  }
13011
+ function formatGadgetLine(info, maxWidth) {
13012
+ const terminalWidth = maxWidth ?? process.stdout.columns ?? 80;
13013
+ const gadgetLabel = import_chalk3.default.magenta.bold(info.name);
13014
+ const timeStr = `${info.elapsedSeconds.toFixed(1)}s`;
13015
+ const timeLabel = import_chalk3.default.dim(timeStr);
13016
+ const fixedLength = 3 + info.name.length + 2 + 1 + timeStr.length;
13017
+ const availableForParams = Math.max(40, terminalWidth - fixedLength - 3);
13018
+ const paramsStr = formatParametersInline(info.parameters, availableForParams);
13019
+ const paramsLabel = paramsStr ? `${import_chalk3.default.dim("(")}${paramsStr}${import_chalk3.default.dim(")")}` : "";
13020
+ if (info.error) {
13021
+ const errorMsg = info.error.length > 50 ? `${info.error.slice(0, 50)}\u2026` : info.error;
13022
+ return `${import_chalk3.default.red("\u2717")} ${gadgetLabel}${paramsLabel} ${import_chalk3.default.red("error:")} ${errorMsg} ${timeLabel}`;
13023
+ }
13024
+ if (!info.isComplete) {
13025
+ return `${import_chalk3.default.blue("\u23F5")} ${gadgetLabel}${paramsLabel} ${timeLabel}`;
13026
+ }
13027
+ let outputLabel;
13028
+ if (info.tokenCount !== void 0 && info.tokenCount > 0) {
13029
+ outputLabel = import_chalk3.default.dim("\u2193") + import_chalk3.default.green(` ${formatTokens(info.tokenCount)} `);
13030
+ } else if (info.outputBytes !== void 0 && info.outputBytes > 0) {
13031
+ outputLabel = import_chalk3.default.green(formatBytes(info.outputBytes)) + " ";
13032
+ } else {
13033
+ outputLabel = "";
13034
+ }
13035
+ const icon = info.breaksLoop ? import_chalk3.default.yellow("\u23F9") : import_chalk3.default.green("\u2713");
13036
+ const nameRef = import_chalk3.default.magenta(info.name);
13037
+ const line1 = `${icon} ${gadgetLabel}${paramsLabel}`;
13038
+ const line2Prefix = ` ${import_chalk3.default.dim("\u2192")} ${nameRef} ${outputLabel}`;
13039
+ const line2 = `${line2Prefix}${timeLabel}`;
13040
+ return `${line1}
13041
+ ${line2}`;
13042
+ }
13015
13043
  function formatBytes(bytes) {
13016
13044
  if (bytes < 1024) {
13017
13045
  return `${bytes} bytes`;
@@ -13021,6 +13049,11 @@ function formatBytes(bytes) {
13021
13049
  }
13022
13050
  return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
13023
13051
  }
13052
+ function truncateOutputPreview(output, maxWidth) {
13053
+ const normalized = output.replace(/\s+/g, " ").trim();
13054
+ if (normalized.length <= maxWidth) return normalized;
13055
+ return normalized.slice(0, maxWidth - 1) + "\u2026";
13056
+ }
13024
13057
  function getMediaIcon(kind) {
13025
13058
  switch (kind) {
13026
13059
  case "image":
@@ -13048,37 +13081,99 @@ function formatGadgetSummary2(result) {
13048
13081
  const gadgetLabel = import_chalk3.default.magenta.bold(result.gadgetName);
13049
13082
  const timeStr = result.executionTimeMs >= 1e3 ? `${(result.executionTimeMs / 1e3).toFixed(1)}s` : `${Math.round(result.executionTimeMs)}ms`;
13050
13083
  const timeLabel = import_chalk3.default.dim(timeStr);
13051
- let outputStr;
13052
- if (result.tokenCount !== void 0 && result.tokenCount > 0) {
13053
- outputStr = `${formatTokens(result.tokenCount)} tokens`;
13054
- } else if (result.result) {
13084
+ const fixedLength = 3 + result.gadgetName.length + 2;
13085
+ const availableForParams = Math.max(40, terminalWidth - fixedLength - 3);
13086
+ const paramsStr = formatParametersInline(result.parameters, availableForParams);
13087
+ const paramsLabel = paramsStr ? `${import_chalk3.default.dim("(")}${paramsStr}${import_chalk3.default.dim(")")}` : "";
13088
+ const icon = result.breaksLoop ? import_chalk3.default.yellow("\u23F9") : result.error ? import_chalk3.default.red("\u2717") : import_chalk3.default.green("\u2713");
13089
+ const line1 = `${icon} ${gadgetLabel}${paramsLabel}`;
13090
+ const nameRef = import_chalk3.default.magenta(result.gadgetName);
13091
+ const hasSubagentMetrics = result.subagentMetrics && result.subagentMetrics.callCount > 0;
13092
+ let outputLabel;
13093
+ let outputStrRaw;
13094
+ if (!hasSubagentMetrics && result.tokenCount !== void 0 && result.tokenCount > 0) {
13095
+ const tokenStr = formatTokens(result.tokenCount);
13096
+ outputLabel = import_chalk3.default.dim("\u2193") + import_chalk3.default.green(` ${tokenStr} `);
13097
+ outputStrRaw = `\u2193 ${tokenStr} `;
13098
+ } else if (!hasSubagentMetrics && result.result) {
13055
13099
  const outputBytes = Buffer.byteLength(result.result, "utf-8");
13056
- outputStr = outputBytes > 0 ? formatBytes(outputBytes) : "no output";
13100
+ if (outputBytes > 0) {
13101
+ const bytesStr = formatBytes(outputBytes);
13102
+ outputLabel = import_chalk3.default.green(bytesStr) + " ";
13103
+ outputStrRaw = bytesStr + " ";
13104
+ } else {
13105
+ outputLabel = "";
13106
+ outputStrRaw = "";
13107
+ }
13057
13108
  } else {
13058
- outputStr = "no output";
13109
+ outputLabel = "";
13110
+ outputStrRaw = "";
13059
13111
  }
13060
- const fixedLength = 2 + result.gadgetName.length + 2 + 3 + outputStr.length + 1 + timeStr.length;
13061
- const availableForParams = Math.max(40, terminalWidth - fixedLength - 2);
13062
- const paramsStr = formatParametersInline(result.parameters, availableForParams);
13063
- const paramsLabel = paramsStr ? `${import_chalk3.default.dim("(")}${paramsStr}${import_chalk3.default.dim(")")}` : "";
13064
13112
  if (result.error) {
13065
13113
  const errorMsg = result.error.length > 50 ? `${result.error.slice(0, 50)}\u2026` : result.error;
13066
- return `${import_chalk3.default.red("\u2717")} ${gadgetLabel}${paramsLabel} ${import_chalk3.default.red("error:")} ${errorMsg} ${timeLabel}`;
13114
+ const line22 = ` ${import_chalk3.default.dim("\u2192")} ${nameRef} ${import_chalk3.default.red("error:")} ${errorMsg} ${timeLabel}`;
13115
+ return `${line1}
13116
+ ${line22}`;
13117
+ }
13118
+ const previewWidth = Math.floor(terminalWidth * 0.6);
13119
+ const prefixLength = 4 + result.gadgetName.length + 1 + outputStrRaw.length + 1 + timeStr.length + 2;
13120
+ const availablePreview = Math.max(20, previewWidth - prefixLength);
13121
+ let customPreview;
13122
+ if (result.gadgetName === "TodoUpsert" && result.parameters?.content) {
13123
+ const statusEmoji = result.parameters.status === "done" ? "\u2705" : result.parameters.status === "in_progress" ? "\u{1F504}" : "\u2B1C";
13124
+ const content = String(result.parameters.content);
13125
+ customPreview = `${statusEmoji} ${truncateOutputPreview(content, availablePreview - 3)}`;
13126
+ }
13127
+ if (result.gadgetName === "GoogleSearch" && result.parameters?.query) {
13128
+ const query = String(result.parameters.query);
13129
+ const countMatch = result.result?.match(/\((\d+)\s+of\s+[\d,]+\s+results?\)/i) || // "(10 of 36400000 results)"
13130
+ result.result?.match(/(\d+)\s+results?\s+found/i) || // "10 results found"
13131
+ result.result?.match(/found\s+(\d+)\s+results?/i);
13132
+ const count = countMatch?.[1] ?? (result.parameters.maxResults ? String(result.parameters.maxResults) : null);
13133
+ const countStr = count ? ` \u2192 ${count} results` : "";
13134
+ const queryPreview = truncateOutputPreview(query, availablePreview - 5 - countStr.length);
13135
+ customPreview = `\u{1F50D} "${queryPreview}"${countStr}`;
13136
+ }
13137
+ let subagentMetricsStr = "";
13138
+ if (result.subagentMetrics && result.subagentMetrics.callCount > 0) {
13139
+ const parts = [];
13140
+ const m = result.subagentMetrics;
13141
+ if (m.inputTokens > 0) {
13142
+ parts.push(import_chalk3.default.dim("\u2191") + import_chalk3.default.yellow(` ${formatTokens(m.inputTokens)}`));
13143
+ }
13144
+ if (m.cachedInputTokens > 0) {
13145
+ parts.push(import_chalk3.default.dim("\u27F3") + import_chalk3.default.blue(` ${formatTokens(m.cachedInputTokens)}`));
13146
+ }
13147
+ if (m.outputTokens > 0) {
13148
+ parts.push(import_chalk3.default.dim("\u2193") + import_chalk3.default.green(` ${formatTokens(m.outputTokens)}`));
13149
+ }
13150
+ if (m.cost > 0) {
13151
+ parts.push(import_chalk3.default.cyan(`$${formatCost(m.cost)}`));
13152
+ }
13153
+ if (parts.length > 0) {
13154
+ subagentMetricsStr = parts.join(import_chalk3.default.dim(" | ")) + import_chalk3.default.dim(" | ");
13155
+ }
13156
+ }
13157
+ let line2;
13158
+ const previewContent = customPreview ?? (result.result?.trim() ? truncateOutputPreview(result.result, availablePreview) : null);
13159
+ if (previewContent) {
13160
+ line2 = ` ${import_chalk3.default.dim("\u2192")} ${nameRef} ${outputLabel}${subagentMetricsStr}${timeLabel}${import_chalk3.default.dim(":")} ${import_chalk3.default.dim(previewContent)}`;
13161
+ } else {
13162
+ line2 = ` ${import_chalk3.default.dim("\u2192")} ${nameRef} ${outputLabel}${subagentMetricsStr}${timeLabel}`;
13067
13163
  }
13068
- const outputLabel = outputStr === "no output" ? import_chalk3.default.dim(outputStr) : import_chalk3.default.green(outputStr);
13069
- const icon = result.breaksLoop ? import_chalk3.default.yellow("\u23F9") : import_chalk3.default.green("\u2713");
13070
- let summaryLine = `${icon} ${gadgetLabel}${paramsLabel} ${import_chalk3.default.dim("\u2192")} ${outputLabel} ${timeLabel}`;
13164
+ let output = `${line1}
13165
+ ${line2}`;
13071
13166
  if (result.media && result.media.length > 0) {
13072
13167
  const mediaLines = result.media.map(formatMediaLine);
13073
- summaryLine += "\n" + mediaLines.join("\n");
13168
+ output += "\n" + mediaLines.join("\n");
13074
13169
  }
13075
13170
  if (result.gadgetName === "TellUser" && result.parameters?.message) {
13076
13171
  const message = String(result.parameters.message);
13077
13172
  const rendered = renderMarkdownWithSeparators(message);
13078
- return `${summaryLine}
13173
+ return `${output}
13079
13174
  ${rendered}`;
13080
13175
  }
13081
- return summaryLine;
13176
+ return output;
13082
13177
  }
13083
13178
 
13084
13179
  // src/cli/utils.ts
@@ -13275,14 +13370,15 @@ var StreamProgress = class {
13275
13370
  * Add a nested agent LLM call (called when nested llm_call_start event received).
13276
13371
  * Used to display hierarchical progress for subagent gadgets.
13277
13372
  */
13278
- addNestedAgent(id, parentInvocationId, depth, model, iteration, inputTokens) {
13373
+ addNestedAgent(id, parentInvocationId, depth, model, iteration, info) {
13279
13374
  this.nestedAgents.set(id, {
13280
13375
  parentInvocationId,
13281
13376
  depth,
13282
13377
  model,
13283
13378
  iteration,
13284
13379
  startTime: Date.now(),
13285
- inputTokens
13380
+ inputTokens: info?.inputTokens,
13381
+ cachedInputTokens: info?.cachedInputTokens
13286
13382
  });
13287
13383
  if (this.isRunning && this.isTTY) {
13288
13384
  this.render();
@@ -13290,11 +13386,36 @@ var StreamProgress = class {
13290
13386
  }
13291
13387
  /**
13292
13388
  * Update a nested agent with completion info (called when nested llm_call_end event received).
13389
+ * Records completion time to freeze the elapsed timer.
13390
+ * @param info - Full LLM call info including tokens, cache details, and cost
13293
13391
  */
13294
- updateNestedAgent(id, outputTokens) {
13392
+ updateNestedAgent(id, info) {
13295
13393
  const agent = this.nestedAgents.get(id);
13296
13394
  if (agent) {
13297
- agent.outputTokens = outputTokens;
13395
+ if (info.inputTokens !== void 0) agent.inputTokens = info.inputTokens;
13396
+ if (info.outputTokens !== void 0) agent.outputTokens = info.outputTokens;
13397
+ if (info.cachedInputTokens !== void 0) agent.cachedInputTokens = info.cachedInputTokens;
13398
+ if (info.cacheCreationInputTokens !== void 0)
13399
+ agent.cacheCreationInputTokens = info.cacheCreationInputTokens;
13400
+ if (info.finishReason !== void 0) agent.finishReason = info.finishReason;
13401
+ if (info.cost !== void 0) {
13402
+ agent.cost = info.cost;
13403
+ } else if (this.modelRegistry && agent.model && agent.outputTokens) {
13404
+ try {
13405
+ const modelName = agent.model.includes(":") ? agent.model.split(":")[1] : agent.model;
13406
+ const costResult = this.modelRegistry.estimateCost(
13407
+ modelName,
13408
+ agent.inputTokens ?? 0,
13409
+ agent.outputTokens,
13410
+ agent.cachedInputTokens,
13411
+ agent.cacheCreationInputTokens
13412
+ );
13413
+ agent.cost = costResult?.totalCost;
13414
+ } catch {
13415
+ }
13416
+ }
13417
+ agent.completed = true;
13418
+ agent.completedTime = Date.now();
13298
13419
  if (this.isRunning && this.isTTY) {
13299
13420
  this.render();
13300
13421
  }
@@ -13309,14 +13430,36 @@ var StreamProgress = class {
13309
13430
  this.render();
13310
13431
  }
13311
13432
  }
13433
+ /**
13434
+ * Get aggregated metrics from all nested agents for a parent gadget.
13435
+ * Used to show total token counts and cost for subagent gadgets like BrowseWeb.
13436
+ */
13437
+ getAggregatedSubagentMetrics(parentInvocationId) {
13438
+ let inputTokens = 0;
13439
+ let outputTokens = 0;
13440
+ let cachedInputTokens = 0;
13441
+ let cost = 0;
13442
+ let callCount = 0;
13443
+ for (const [, nested] of this.nestedAgents) {
13444
+ if (nested.parentInvocationId === parentInvocationId) {
13445
+ inputTokens += nested.inputTokens ?? 0;
13446
+ outputTokens += nested.outputTokens ?? 0;
13447
+ cachedInputTokens += nested.cachedInputTokens ?? 0;
13448
+ cost += nested.cost ?? 0;
13449
+ callCount++;
13450
+ }
13451
+ }
13452
+ return { inputTokens, outputTokens, cachedInputTokens, cost, callCount };
13453
+ }
13312
13454
  /**
13313
13455
  * Add a nested gadget call (called when nested gadget_call event received).
13314
13456
  */
13315
- addNestedGadget(id, depth, parentInvocationId, name) {
13457
+ addNestedGadget(id, depth, parentInvocationId, name, parameters) {
13316
13458
  this.nestedGadgets.set(id, {
13317
13459
  depth,
13318
13460
  parentInvocationId,
13319
13461
  name,
13462
+ parameters,
13320
13463
  startTime: Date.now()
13321
13464
  });
13322
13465
  if (this.isRunning && this.isTTY) {
@@ -13334,11 +13477,13 @@ var StreamProgress = class {
13334
13477
  }
13335
13478
  /**
13336
13479
  * Mark a nested gadget as completed (keeps it visible with ✓ indicator).
13480
+ * Records completion time to freeze the elapsed timer.
13337
13481
  */
13338
13482
  completeNestedGadget(id) {
13339
13483
  const gadget = this.nestedGadgets.get(id);
13340
13484
  if (gadget) {
13341
13485
  gadget.completed = true;
13486
+ gadget.completedTime = Date.now();
13342
13487
  if (this.isRunning && this.isTTY) {
13343
13488
  this.render();
13344
13489
  }
@@ -13473,38 +13618,130 @@ var StreamProgress = class {
13473
13618
  this.clearRenderedLines();
13474
13619
  const spinner = SPINNER_FRAMES[this.frameIndex++ % SPINNER_FRAMES.length];
13475
13620
  const lines = [];
13476
- if (this.mode === "streaming") {
13477
- lines.push(this.formatStreamingLine(spinner));
13478
- } else {
13479
- lines.push(this.formatCumulativeLine(spinner));
13480
- }
13621
+ const activeNestedStreams = [];
13481
13622
  if (this.isTTY) {
13482
13623
  for (const [gadgetId, gadget] of this.inFlightGadgets) {
13483
- const elapsed = ((Date.now() - gadget.startTime) / 1e3).toFixed(1);
13484
- const gadgetLine = ` ${import_chalk4.default.blue("\u23F5")} ${import_chalk4.default.magenta.bold(gadget.name)}${import_chalk4.default.dim("(...)")} ${import_chalk4.default.dim(elapsed + "s")}`;
13624
+ const elapsedSeconds = (Date.now() - gadget.startTime) / 1e3;
13625
+ const termWidth = process.stdout.columns ?? 80;
13626
+ const gadgetIndent = " ";
13627
+ const line = formatGadgetLine(
13628
+ {
13629
+ name: gadget.name,
13630
+ parameters: gadget.params,
13631
+ elapsedSeconds,
13632
+ isComplete: false
13633
+ },
13634
+ termWidth - gadgetIndent.length
13635
+ );
13636
+ const gadgetLine = line.split("\n").map((l) => gadgetIndent + l).join("\n");
13485
13637
  lines.push(gadgetLine);
13638
+ const nestedOps = [];
13486
13639
  for (const [_agentId, nested] of this.nestedAgents) {
13487
- if (nested.parentInvocationId !== gadgetId) continue;
13488
- const indent = " ".repeat(nested.depth + 1);
13489
- const nestedElapsed = ((Date.now() - nested.startTime) / 1e3).toFixed(1);
13490
- const tokens = nested.inputTokens ? ` ${import_chalk4.default.dim("\u2191")}${import_chalk4.default.yellow(formatTokens(nested.inputTokens))}` : "";
13491
- const outTokens = nested.outputTokens ? ` ${import_chalk4.default.dim("\u2193")}${import_chalk4.default.green(formatTokens(nested.outputTokens))}` : "";
13492
- const nestedLine = `${indent}${import_chalk4.default.cyan(`#${nested.iteration}`)} ${import_chalk4.default.dim(nested.model)}${tokens}${outTokens} ${import_chalk4.default.dim(nestedElapsed + "s")} ${import_chalk4.default.cyan(spinner)}`;
13493
- lines.push(nestedLine);
13494
- }
13495
- for (const [nestedId, nestedGadget] of this.nestedGadgets) {
13640
+ if (nested.parentInvocationId === gadgetId) {
13641
+ nestedOps.push({
13642
+ type: "agent",
13643
+ startTime: nested.startTime,
13644
+ depth: nested.depth,
13645
+ iteration: nested.iteration,
13646
+ model: nested.model,
13647
+ inputTokens: nested.inputTokens,
13648
+ cachedInputTokens: nested.cachedInputTokens,
13649
+ outputTokens: nested.outputTokens,
13650
+ cost: nested.cost,
13651
+ finishReason: nested.finishReason,
13652
+ completed: nested.completed,
13653
+ completedTime: nested.completedTime
13654
+ });
13655
+ if (!nested.completed) {
13656
+ activeNestedStreams.push({
13657
+ depth: nested.depth,
13658
+ iteration: nested.iteration,
13659
+ model: nested.model,
13660
+ inputTokens: nested.inputTokens,
13661
+ cachedInputTokens: nested.cachedInputTokens,
13662
+ outputTokens: nested.outputTokens,
13663
+ cost: nested.cost,
13664
+ startTime: nested.startTime
13665
+ });
13666
+ }
13667
+ }
13668
+ }
13669
+ for (const [_nestedId, nestedGadget] of this.nestedGadgets) {
13496
13670
  if (nestedGadget.parentInvocationId === gadgetId) {
13497
- const indent = " ".repeat(nestedGadget.depth + 1);
13498
- const nestedElapsed = ((Date.now() - nestedGadget.startTime) / 1e3).toFixed(1);
13499
- const icon = nestedGadget.completed ? import_chalk4.default.green("\u2713") : import_chalk4.default.blue("\u23F5");
13500
- const nestedGadgetLine = `${indent}${icon} ${import_chalk4.default.dim(nestedGadget.name + "(...)")} ${import_chalk4.default.dim(nestedElapsed + "s")}`;
13501
- lines.push(nestedGadgetLine);
13671
+ nestedOps.push({
13672
+ type: "gadget",
13673
+ startTime: nestedGadget.startTime,
13674
+ depth: nestedGadget.depth,
13675
+ name: nestedGadget.name,
13676
+ parameters: nestedGadget.parameters,
13677
+ completed: nestedGadget.completed,
13678
+ completedTime: nestedGadget.completedTime
13679
+ });
13680
+ }
13681
+ }
13682
+ nestedOps.sort((a, b) => a.startTime - b.startTime);
13683
+ for (const op of nestedOps) {
13684
+ if (op.type === "agent" && !op.completed) {
13685
+ continue;
13686
+ }
13687
+ const indent = " ".repeat(op.depth + 2);
13688
+ const endTime = op.completedTime ?? Date.now();
13689
+ const elapsedSeconds2 = (endTime - op.startTime) / 1e3;
13690
+ if (op.type === "agent") {
13691
+ const line2 = formatLLMCallLine({
13692
+ iteration: op.iteration ?? 0,
13693
+ model: op.model ?? "",
13694
+ inputTokens: op.inputTokens,
13695
+ cachedInputTokens: op.cachedInputTokens,
13696
+ outputTokens: op.outputTokens,
13697
+ elapsedSeconds: elapsedSeconds2,
13698
+ cost: op.cost,
13699
+ finishReason: op.completed ? op.finishReason ?? "stop" : void 0,
13700
+ isStreaming: !op.completed,
13701
+ spinner
13702
+ });
13703
+ lines.push(`${indent}${line2}`);
13704
+ } else {
13705
+ const termWidth2 = process.stdout.columns ?? 80;
13706
+ const line2 = formatGadgetLine(
13707
+ {
13708
+ name: op.name ?? "",
13709
+ parameters: op.parameters,
13710
+ elapsedSeconds: elapsedSeconds2,
13711
+ isComplete: op.completed ?? false
13712
+ },
13713
+ termWidth2 - indent.length
13714
+ );
13715
+ const indentedLine = line2.split("\n").map((l) => indent + l).join("\n");
13716
+ lines.push(indentedLine);
13502
13717
  }
13503
13718
  }
13504
13719
  }
13505
13720
  }
13506
- this.lastRenderLineCount = lines.length;
13507
- this.target.write("\r" + lines.join("\n"));
13721
+ for (const stream2 of activeNestedStreams) {
13722
+ const indent = " ".repeat(stream2.depth + 2);
13723
+ const elapsedSeconds = (Date.now() - stream2.startTime) / 1e3;
13724
+ const line = formatLLMCallLine({
13725
+ iteration: stream2.iteration,
13726
+ model: stream2.model,
13727
+ inputTokens: stream2.inputTokens,
13728
+ cachedInputTokens: stream2.cachedInputTokens,
13729
+ outputTokens: stream2.outputTokens,
13730
+ elapsedSeconds,
13731
+ cost: stream2.cost,
13732
+ isStreaming: true,
13733
+ spinner
13734
+ });
13735
+ lines.push(`${indent}${line}`);
13736
+ }
13737
+ if (this.mode === "streaming") {
13738
+ lines.push(this.formatStreamingLine(spinner));
13739
+ } else {
13740
+ lines.push(this.formatCumulativeLine(spinner));
13741
+ }
13742
+ const output = lines.join("\n");
13743
+ this.lastRenderLineCount = (output.match(/\n/g) || []).length + 1;
13744
+ this.target.write("\r" + output);
13508
13745
  this.hasRendered = true;
13509
13746
  }
13510
13747
  /**
@@ -13520,42 +13757,27 @@ var StreamProgress = class {
13520
13757
  }
13521
13758
  /**
13522
13759
  * Format the streaming mode progress line (returns string, doesn't write).
13760
+ * Uses the shared formatLLMCallLine() function for consistent formatting
13761
+ * between main agent and nested subagent displays.
13523
13762
  */
13524
13763
  formatStreamingLine(spinner) {
13525
- const elapsed = ((Date.now() - this.callStartTime) / 1e3).toFixed(1);
13526
13764
  const outTokens = this.callOutputTokensEstimated ? Math.round(this.callOutputChars / FALLBACK_CHARS_PER_TOKEN) : this.callOutputTokens;
13527
- const parts = [];
13528
- const iterPart = import_chalk4.default.cyan(`#${this.currentIteration}`);
13529
- if (this.model) {
13530
- parts.push(`${iterPart} ${import_chalk4.default.magenta(this.model)}`);
13531
- } else {
13532
- parts.push(iterPart);
13533
- }
13534
- const usagePercent = this.getContextUsagePercent();
13535
- if (usagePercent !== null) {
13536
- const formatted = `${Math.round(usagePercent)}%`;
13537
- if (usagePercent >= 80) {
13538
- parts.push(import_chalk4.default.red(formatted));
13539
- } else if (usagePercent >= 50) {
13540
- parts.push(import_chalk4.default.yellow(formatted));
13541
- } else {
13542
- parts.push(import_chalk4.default.green(formatted));
13765
+ return formatLLMCallLine({
13766
+ iteration: this.currentIteration,
13767
+ model: this.model ?? "",
13768
+ inputTokens: this.callInputTokens,
13769
+ cachedInputTokens: this.callCachedInputTokens,
13770
+ outputTokens: outTokens,
13771
+ elapsedSeconds: (Date.now() - this.callStartTime) / 1e3,
13772
+ cost: this.calculateCurrentCallCost(outTokens),
13773
+ isStreaming: true,
13774
+ spinner,
13775
+ contextPercent: this.getContextUsagePercent(),
13776
+ estimated: {
13777
+ input: this.callInputTokensEstimated,
13778
+ output: this.callOutputTokensEstimated
13543
13779
  }
13544
- }
13545
- if (this.callInputTokens > 0) {
13546
- const prefix = this.callInputTokensEstimated ? "~" : "";
13547
- parts.push(import_chalk4.default.dim("\u2191") + import_chalk4.default.yellow(` ${prefix}${formatTokens(this.callInputTokens)}`));
13548
- }
13549
- if (this.isStreaming || outTokens > 0) {
13550
- const prefix = this.callOutputTokensEstimated ? "~" : "";
13551
- parts.push(import_chalk4.default.dim("\u2193") + import_chalk4.default.green(` ${prefix}${formatTokens(outTokens)}`));
13552
- }
13553
- parts.push(import_chalk4.default.dim(`${elapsed}s`));
13554
- const callCost = this.calculateCurrentCallCost(outTokens);
13555
- if (callCost > 0) {
13556
- parts.push(import_chalk4.default.cyan(`$${formatCost(callCost)}`));
13557
- }
13558
- return `${parts.join(import_chalk4.default.dim(" | "))} ${import_chalk4.default.cyan(spinner)}`;
13780
+ });
13559
13781
  }
13560
13782
  /**
13561
13783
  * Calculates live cost estimate for the current streaming call.
@@ -13783,7 +14005,7 @@ function addAgentOptions(cmd, defaults) {
13783
14005
  OPTION_FLAGS.logLlmRequests,
13784
14006
  OPTION_DESCRIPTIONS.logLlmRequests,
13785
14007
  defaults?.["log-llm-requests"]
13786
- ).option(OPTION_FLAGS.inputImage, OPTION_DESCRIPTIONS.inputImage).option(OPTION_FLAGS.inputAudio, OPTION_DESCRIPTIONS.inputAudio).option(OPTION_FLAGS.docker, OPTION_DESCRIPTIONS.docker).option(OPTION_FLAGS.dockerRo, OPTION_DESCRIPTIONS.dockerRo).option(OPTION_FLAGS.noDocker, OPTION_DESCRIPTIONS.noDocker).option(OPTION_FLAGS.dockerDev, OPTION_DESCRIPTIONS.dockerDev);
14008
+ ).option(OPTION_FLAGS.inputImage, OPTION_DESCRIPTIONS.inputImage).option(OPTION_FLAGS.inputAudio, OPTION_DESCRIPTIONS.inputAudio).option(OPTION_FLAGS.docker, OPTION_DESCRIPTIONS.docker).option(OPTION_FLAGS.dockerRo, OPTION_DESCRIPTIONS.dockerRo).option(OPTION_FLAGS.noDocker, OPTION_DESCRIPTIONS.noDocker);
13787
14009
  }
13788
14010
  function configToCompleteOptions(config) {
13789
14011
  const result = {};
@@ -13860,8 +14082,7 @@ async function executeAgent(promptArg, options, env) {
13860
14082
  const dockerOptions = {
13861
14083
  docker: options.docker ?? false,
13862
14084
  dockerRo: options.dockerRo ?? false,
13863
- noDocker: options.noDocker ?? false,
13864
- dockerDev: options.dockerDev ?? false
14085
+ noDocker: options.noDocker ?? false
13865
14086
  };
13866
14087
  const dockerEnabled = resolveDockerEnabled(
13867
14088
  env.dockerConfig,
@@ -13870,7 +14091,6 @@ async function executeAgent(promptArg, options, env) {
13870
14091
  // Profile-level docker: true/false
13871
14092
  );
13872
14093
  if (dockerEnabled) {
13873
- const devMode = resolveDevMode(env.dockerConfig, dockerOptions.dockerDev);
13874
14094
  const ctx = createDockerContext(
13875
14095
  env.dockerConfig,
13876
14096
  dockerOptions,
@@ -13881,7 +14101,7 @@ async function executeAgent(promptArg, options, env) {
13881
14101
  // Profile-level CWD permission override
13882
14102
  );
13883
14103
  try {
13884
- await executeInDocker(ctx, devMode);
14104
+ await executeInDocker(ctx);
13885
14105
  } catch (error) {
13886
14106
  if (error instanceof DockerSkipError) {
13887
14107
  } else {
@@ -13908,9 +14128,18 @@ async function executeAgent(promptArg, options, env) {
13908
14128
  registry.registerByClass(gadget);
13909
14129
  }
13910
14130
  }
14131
+ if (!options.quiet) {
14132
+ const allNames = registry.getAll().map((g) => g.name).join(", ");
14133
+ env.stderr.write(import_chalk5.default.dim(`Gadgets: ${allNames}
14134
+ `));
14135
+ }
13911
14136
  const printer = new StreamPrinter(env.stdout);
13912
14137
  const stderrTTY = env.stderr.isTTY === true;
13913
- const progress = new StreamProgress(env.stderr, stderrTTY, client.modelRegistry);
14138
+ const progress = new StreamProgress(
14139
+ env.stderr,
14140
+ stderrTTY,
14141
+ client.modelRegistry
14142
+ );
13914
14143
  const abortController = new AbortController();
13915
14144
  let wasCancelled = false;
13916
14145
  let isStreaming = false;
@@ -13920,9 +14149,11 @@ async function executeAgent(promptArg, options, env) {
13920
14149
  wasCancelled = true;
13921
14150
  abortController.abort();
13922
14151
  progress.pause();
13923
- env.stderr.write(import_chalk5.default.yellow(`
14152
+ env.stderr.write(
14153
+ import_chalk5.default.yellow(`
13924
14154
  [Cancelled] ${progress.formatStats()}
13925
- `));
14155
+ `)
14156
+ );
13926
14157
  } else {
13927
14158
  handleQuit();
13928
14159
  }
@@ -13932,7 +14163,11 @@ async function executeAgent(promptArg, options, env) {
13932
14163
  cleanupSigint: null,
13933
14164
  restore: () => {
13934
14165
  if (stdinIsInteractive && stdinStream.isTTY && !wasCancelled) {
13935
- keyboard.cleanupEsc = createEscKeyListener(stdinStream, handleCancel, handleCancel);
14166
+ keyboard.cleanupEsc = createEscKeyListener(
14167
+ stdinStream,
14168
+ handleCancel,
14169
+ handleCancel
14170
+ );
13936
14171
  }
13937
14172
  }
13938
14173
  };
@@ -13957,7 +14192,11 @@ async function executeAgent(promptArg, options, env) {
13957
14192
  process.exit(130);
13958
14193
  };
13959
14194
  if (stdinIsInteractive && stdinStream.isTTY) {
13960
- keyboard.cleanupEsc = createEscKeyListener(stdinStream, handleCancel, handleCancel);
14195
+ keyboard.cleanupEsc = createEscKeyListener(
14196
+ stdinStream,
14197
+ handleCancel,
14198
+ handleCancel
14199
+ );
13961
14200
  }
13962
14201
  keyboard.cleanupSigint = createSigintListener(
13963
14202
  handleCancel,
@@ -13983,7 +14222,12 @@ async function executeAgent(promptArg, options, env) {
13983
14222
  gadgetApprovals,
13984
14223
  defaultMode: "allowed"
13985
14224
  };
13986
- const approvalManager = new ApprovalManager(approvalConfig, env, progress, keyboard);
14225
+ const approvalManager = new ApprovalManager(
14226
+ approvalConfig,
14227
+ env,
14228
+ progress,
14229
+ keyboard
14230
+ );
13987
14231
  let usage;
13988
14232
  let iterations = 0;
13989
14233
  const llmLogsBaseDir = resolveLogDir(options.logLlmRequests, "requests");
@@ -13993,7 +14237,10 @@ async function executeAgent(promptArg, options, env) {
13993
14237
  try {
13994
14238
  return await client.countTokens(model, messages);
13995
14239
  } catch {
13996
- const totalChars = messages.reduce((sum, m) => sum + (m.content?.length ?? 0), 0);
14240
+ const totalChars = messages.reduce(
14241
+ (sum, m) => sum + (m.content?.length ?? 0),
14242
+ 0
14243
+ );
13997
14244
  return Math.round(totalChars / FALLBACK_CHARS_PER_TOKEN);
13998
14245
  }
13999
14246
  };
@@ -14015,7 +14262,9 @@ async function executeAgent(promptArg, options, env) {
14015
14262
  observers: {
14016
14263
  // onLLMCallStart: Start progress indicator for each LLM call
14017
14264
  // This showcases how to react to agent lifecycle events
14265
+ // Skip for subagent events (tracked separately via nested display)
14018
14266
  onLLMCallStart: async (context) => {
14267
+ if (context.subagentContext) return;
14019
14268
  isStreaming = true;
14020
14269
  llmCallCounter++;
14021
14270
  const inputTokens = await countMessagesTokens(
@@ -14041,7 +14290,9 @@ async function executeAgent(promptArg, options, env) {
14041
14290
  },
14042
14291
  // onStreamChunk: Real-time updates as LLM generates tokens
14043
14292
  // This enables responsive UIs that show progress during generation
14293
+ // Skip for subagent events (tracked separately via nested display)
14044
14294
  onStreamChunk: async (context) => {
14295
+ if (context.subagentContext) return;
14045
14296
  progress.update(context.accumulatedText.length);
14046
14297
  if (context.usage) {
14047
14298
  if (context.usage.inputTokens) {
@@ -14058,7 +14309,9 @@ async function executeAgent(promptArg, options, env) {
14058
14309
  },
14059
14310
  // onLLMCallComplete: Finalize metrics after each LLM call
14060
14311
  // This is where you'd typically log metrics or update dashboards
14312
+ // Skip progress updates for subagent events (tracked separately via nested display)
14061
14313
  onLLMCallComplete: async (context) => {
14314
+ if (context.subagentContext) return;
14062
14315
  isStreaming = false;
14063
14316
  usage = context.usage;
14064
14317
  iterations = Math.max(iterations, context.iteration + 1);
@@ -14087,7 +14340,7 @@ async function executeAgent(promptArg, options, env) {
14087
14340
  }
14088
14341
  const callElapsed = progress.getCallElapsedSeconds();
14089
14342
  progress.endCall(context.usage);
14090
- if (!options.quiet) {
14343
+ if (!options.quiet && !context.subagentContext) {
14091
14344
  const summary = renderSummary({
14092
14345
  iterations: context.iteration + 1,
14093
14346
  model: options.model,
@@ -14100,6 +14353,7 @@ async function executeAgent(promptArg, options, env) {
14100
14353
  env.stderr.write(`${summary}
14101
14354
  `);
14102
14355
  }
14356
+ env.stderr.write("\n");
14103
14357
  }
14104
14358
  if (llmSessionDir) {
14105
14359
  const filename = `${formatCallNumber(llmCallCounter)}.response`;
@@ -14145,7 +14399,10 @@ ${ctx.gadgetName} is denied by configuration.`
14145
14399
  }
14146
14400
  return { action: "proceed" };
14147
14401
  }
14148
- const result = await approvalManager.requestApproval(ctx.gadgetName, ctx.parameters);
14402
+ const result = await approvalManager.requestApproval(
14403
+ ctx.gadgetName,
14404
+ ctx.parameters
14405
+ );
14149
14406
  if (!result.approved) {
14150
14407
  return {
14151
14408
  action: "skip",
@@ -14188,11 +14445,11 @@ Denied: ${result.reason ?? "by user"}`
14188
14445
  builder.withSyntheticGadgetCall(
14189
14446
  "TellUser",
14190
14447
  {
14191
- message: "\u{1F44B} Hello! I'm ready to help.\n\nHere's what I can do:\n- Analyze your codebase\n- Execute commands\n- Answer questions\n\nWhat would you like me to work on?",
14448
+ message: "\u{1F44B} Hello! I'm ready to help.\n\nWhat would you like me to work on?",
14192
14449
  done: false,
14193
14450
  type: "info"
14194
14451
  },
14195
- "\u2139\uFE0F \u{1F44B} Hello! I'm ready to help.\n\nHere's what I can do:\n- Analyze your codebase\n- Execute commands\n- Answer questions\n\nWhat would you like me to work on?"
14452
+ "\u2139\uFE0F \u{1F44B} Hello! I'm ready to help.\n\nWhat would you like me to work on?"
14196
14453
  );
14197
14454
  builder.withTextOnlyHandler("acknowledge");
14198
14455
  builder.withTextWithGadgetsHandler({
@@ -14203,8 +14460,7 @@ Denied: ${result.reason ?? "by user"}`
14203
14460
  builder.withTrailingMessage(
14204
14461
  (ctx) => [
14205
14462
  `[Iteration ${ctx.iteration + 1}/${ctx.maxIterations}]`,
14206
- "Think carefully: what gadget invocations can you make in parallel right now?",
14207
- "Maximize efficiency by batching independent operations in a single response."
14463
+ "Think carefully in two steps: 1. what gadget invocations we should be making next? 2. how do they depend on one another so we can run all of them in the right order? Then respond with all the gadget invocations you are able to do now."
14208
14464
  ].join(" ")
14209
14465
  );
14210
14466
  if (!options.quiet) {
@@ -14217,21 +14473,32 @@ Denied: ${result.reason ?? "by user"}`
14217
14473
  subagentEvent.gadgetInvocationId,
14218
14474
  subagentEvent.depth,
14219
14475
  info.model,
14220
- info.iteration,
14221
- info.inputTokens
14476
+ info.iteration + 1,
14477
+ // Make 1-indexed like main agent
14478
+ {
14479
+ inputTokens: info.usage?.inputTokens ?? info.inputTokens,
14480
+ cachedInputTokens: info.usage?.cachedInputTokens
14481
+ }
14222
14482
  );
14223
14483
  } else if (subagentEvent.type === "llm_call_end") {
14224
14484
  const info = subagentEvent.event;
14225
14485
  const subagentId = `${subagentEvent.gadgetInvocationId}:${info.iteration}`;
14226
- progress.updateNestedAgent(subagentId, info.outputTokens);
14227
- setTimeout(() => progress.removeNestedAgent(subagentId), 100);
14486
+ progress.updateNestedAgent(subagentId, {
14487
+ inputTokens: info.usage?.inputTokens ?? info.inputTokens,
14488
+ outputTokens: info.usage?.outputTokens ?? info.outputTokens,
14489
+ cachedInputTokens: info.usage?.cachedInputTokens,
14490
+ cacheCreationInputTokens: info.usage?.cacheCreationInputTokens,
14491
+ finishReason: info.finishReason,
14492
+ cost: info.cost
14493
+ });
14228
14494
  } else if (subagentEvent.type === "gadget_call") {
14229
14495
  const gadgetEvent = subagentEvent.event;
14230
14496
  progress.addNestedGadget(
14231
14497
  gadgetEvent.call.invocationId,
14232
14498
  subagentEvent.depth,
14233
14499
  subagentEvent.gadgetInvocationId,
14234
- gadgetEvent.call.gadgetName
14500
+ gadgetEvent.call.gadgetName,
14501
+ gadgetEvent.call.parameters
14235
14502
  );
14236
14503
  } else if (subagentEvent.type === "gadget_result") {
14237
14504
  const resultEvent = subagentEvent.event;
@@ -14288,10 +14555,23 @@ Denied: ${result.reason ?? "by user"}`
14288
14555
  }
14289
14556
  } else {
14290
14557
  const tokenCount = await countGadgetOutputTokens(event.result.result);
14291
- env.stderr.write(
14292
- `${formatGadgetSummary2({ ...event.result, tokenCount, media: event.result.storedMedia })}
14293
- `
14558
+ const subagentMetrics = progress.getAggregatedSubagentMetrics(
14559
+ event.result.invocationId
14294
14560
  );
14561
+ const summary = formatGadgetSummary2({
14562
+ ...event.result,
14563
+ tokenCount,
14564
+ media: event.result.storedMedia,
14565
+ subagentMetrics: subagentMetrics.callCount > 0 ? subagentMetrics : void 0
14566
+ });
14567
+ if (event.result.gadgetName === "TellUser") {
14568
+ env.stderr.write(`${summary}
14569
+ `);
14570
+ } else {
14571
+ const indentedSummary = summary.split("\n").map((line) => " " + line).join("\n");
14572
+ env.stderr.write(`${indentedSummary}
14573
+ `);
14574
+ }
14295
14575
  }
14296
14576
  if (progress.hasInFlightGadgets()) {
14297
14577
  progress.start();
@@ -14330,7 +14610,10 @@ Denied: ${result.reason ?? "by user"}`
14330
14610
  }
14331
14611
  }
14332
14612
  function registerAgentCommand(program, env, config, globalSubagents) {
14333
- const cmd = program.command(COMMANDS.agent).description("Run the llmist agent loop with optional gadgets.").argument("[prompt]", "Prompt for the agent loop. Falls back to stdin when available.");
14613
+ const cmd = program.command(COMMANDS.agent).description("Run the llmist agent loop with optional gadgets.").argument(
14614
+ "[prompt]",
14615
+ "Prompt for the agent loop. Falls back to stdin when available."
14616
+ );
14334
14617
  addAgentOptions(cmd, config);
14335
14618
  cmd.action(
14336
14619
  (prompt, options) => executeAction(() => {
@@ -14440,7 +14723,7 @@ function registerCompleteCommand(program, env, config) {
14440
14723
 
14441
14724
  // src/cli/init-command.ts
14442
14725
  var import_node_fs12 = require("fs");
14443
- var import_node_path14 = require("path");
14726
+ var import_node_path13 = require("path");
14444
14727
  var STARTER_CONFIG = `# ~/.llmist/cli.toml
14445
14728
  # llmist CLI configuration file
14446
14729
  #
@@ -14496,7 +14779,7 @@ var STARTER_CONFIG = `# ~/.llmist/cli.toml
14496
14779
  `;
14497
14780
  async function executeInit(_options, env) {
14498
14781
  const configPath = getConfigPath();
14499
- const configDir = (0, import_node_path14.dirname)(configPath);
14782
+ const configDir = (0, import_node_path13.dirname)(configPath);
14500
14783
  if ((0, import_node_fs12.existsSync)(configPath)) {
14501
14784
  env.stderr.write(`Configuration already exists at ${configPath}
14502
14785
  `);