@ai-setting/roy-agent-cli 1.7.52 → 1.7.55

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/index.js CHANGED
@@ -364,6 +364,7 @@ var exports_stream_output_service = {};
364
364
  __export(exports_stream_output_service, {
365
365
  streamAbortSignal: () => streamAbortSignal,
366
366
  resetStreamAbort: () => resetStreamAbort,
367
+ hasNumericUsage: () => hasNumericUsage,
367
368
  abortStream: () => abortStream,
368
369
  StreamOutputService: () => StreamOutputService,
369
370
  COLORS: () => COLORS
@@ -376,6 +377,11 @@ function resetStreamAbort() {
376
377
  function abortStream() {
377
378
  streamAbortSignal.aborted = true;
378
379
  }
380
+ function hasNumericUsage(usage) {
381
+ if (!usage)
382
+ return false;
383
+ return typeof usage.promptTokens === "number" || typeof usage.completionTokens === "number" || typeof usage.totalTokens === "number" || typeof usage.cacheRead === "number" || typeof usage.cacheWrite === "number" || typeof usage.promptTokensTotal === "number";
384
+ }
379
385
  var logger2, COLORS, streamAbortSignal, StreamOutputService;
380
386
  var init_stream_output_service = __esm(() => {
381
387
  init_markdown_renderer();
@@ -645,14 +651,16 @@ ${COLORS.system("[已中断]")}
645
651
  completionTokens: metadata.usage?.completionTokens,
646
652
  totalTokens: metadata.usage?.totalTokens,
647
653
  cacheRead: metadata.usage?.cacheRead,
648
- cacheWrite: metadata.usage?.cacheWrite
654
+ cacheWrite: metadata.usage?.cacheWrite,
655
+ promptTokensTotal: metadata.usage?.promptTokensTotal
649
656
  };
650
657
  const logData = {
651
658
  promptTokens: this.usageInfo.promptTokens,
652
659
  completionTokens: this.usageInfo.completionTokens,
653
660
  totalTokens: this.usageInfo.totalTokens,
654
661
  cacheRead: this.usageInfo.cacheRead,
655
- cacheWrite: this.usageInfo.cacheWrite
662
+ cacheWrite: this.usageInfo.cacheWrite,
663
+ promptTokensTotal: this.usageInfo.promptTokensTotal
656
664
  };
657
665
  if (this.contextWindow !== undefined) {
658
666
  logData.contextWindow = this.contextWindow;
@@ -668,7 +676,8 @@ ${COLORS.system("[已中断]")}
668
676
  }
669
677
  this.emitChunk({
670
678
  type: "llm.completed",
671
- fullContent: payload.content ?? this.fullText
679
+ fullContent: payload.content ?? this.fullText,
680
+ usage: hasNumericUsage(this.usageInfo) ? this.usageInfo : undefined
672
681
  });
673
682
  }
674
683
  getFullText() {
@@ -1652,6 +1661,78 @@ var init_StatusBar = __esm(() => {
1652
1661
  init_theme();
1653
1662
  });
1654
1663
 
1664
+ // src/tui/UsageFooter.tsx
1665
+ import { Text as Text4 } from "ink";
1666
+ import { jsxDEV as jsxDEV4 } from "react/jsx-dev-runtime";
1667
+ function formatTokens2(n) {
1668
+ if (!Number.isFinite(n))
1669
+ return "0";
1670
+ if (n < 1000)
1671
+ return String(Math.round(n));
1672
+ return `${(n / 1000).toFixed(1)}k`;
1673
+ }
1674
+ function formatUsageFooter(usage) {
1675
+ if (!usage)
1676
+ return null;
1677
+ const promptTokens = usage.promptTokens;
1678
+ const completionTokens = usage.completionTokens;
1679
+ const cacheRead = usage.cacheRead;
1680
+ const cacheWrite = usage.cacheWrite;
1681
+ const hasCacheRead = typeof cacheRead === "number";
1682
+ const hasCacheWrite = typeof cacheWrite === "number";
1683
+ const hasPrompt = typeof promptTokens === "number";
1684
+ const hasCompletion = typeof completionTokens === "number";
1685
+ if (!hasPrompt && !hasCompletion && !hasCacheRead && !hasCacheWrite) {
1686
+ return null;
1687
+ }
1688
+ const allZero = (promptTokens ?? 0) === 0 && (completionTokens ?? 0) === 0 && (cacheRead ?? 0) === 0 && (cacheWrite ?? 0) === 0;
1689
+ if (allZero)
1690
+ return null;
1691
+ const parts = [];
1692
+ if (hasCacheRead || hasCacheWrite) {
1693
+ const read = cacheRead ?? 0;
1694
+ const write = cacheWrite ?? 0;
1695
+ const reportedTotal = usage.promptTokensTotal;
1696
+ const denom = typeof reportedTotal === "number" && reportedTotal > 0 ? reportedTotal : (promptTokens ?? 0) + read + write;
1697
+ const rawRate = denom > 0 ? read / denom * 100 : 0;
1698
+ const rate = Math.min(100, Math.max(0, rawRate));
1699
+ parts.push(`cache ${rate.toFixed(1)}%`);
1700
+ if (denom > 0) {
1701
+ parts.push(`${formatTokens2(read)}/${formatTokens2(denom)}`);
1702
+ }
1703
+ if (write > 0) {
1704
+ parts.push(`write ${formatTokens2(write)}`);
1705
+ }
1706
+ }
1707
+ if (hasPrompt)
1708
+ parts.push(`in ${formatTokens2(promptTokens)}`);
1709
+ if (hasCompletion)
1710
+ parts.push(`out ${formatTokens2(completionTokens)}`);
1711
+ if (parts.length === 0)
1712
+ return null;
1713
+ return parts.join(" · ");
1714
+ }
1715
+ var UsageFooter = ({
1716
+ usage,
1717
+ fallback,
1718
+ indent = 2
1719
+ }) => {
1720
+ const line = formatUsageFooter(usage);
1721
+ if (!line) {
1722
+ if (!fallback)
1723
+ return null;
1724
+ return /* @__PURE__ */ jsxDEV4(Text4, {
1725
+ dimColor: true,
1726
+ children: `${" ".repeat(indent)}${fallback}`
1727
+ }, undefined, false, undefined, this);
1728
+ }
1729
+ return /* @__PURE__ */ jsxDEV4(Text4, {
1730
+ dimColor: true,
1731
+ children: `${" ".repeat(indent)}${line}`
1732
+ }, undefined, false, undefined, this);
1733
+ };
1734
+ var init_UsageFooter = () => {};
1735
+
1655
1736
  // src/commands/ui/dynamic-tail.ts
1656
1737
  function computeDynamicTailBounds(totalRows, reservedRows = DEFAULT_RESERVED_ROWS, hasPopup = false) {
1657
1738
  const total = Math.max(1, totalRows ?? 24);
@@ -1702,9 +1783,9 @@ __export(exports_repl_ink, {
1702
1783
  MultilineInput: () => MultilineInput
1703
1784
  });
1704
1785
  import * as React3 from "react";
1705
- import { Box as Box3, Text as Text4, Static, useApp, useInput as useInput2 } from "ink";
1706
- import { createLogger as createLogger5 } from "@ai-setting/roy-agent-core";
1707
- import { jsxDEV as jsxDEV4 } from "react/jsx-dev-runtime";
1786
+ import { Box as Box3, Text as Text5, Static, useApp, useInput as useInput2 } from "ink";
1787
+ import { createLogger as createLogger6 } from "@ai-setting/roy-agent-core";
1788
+ import { jsxDEV as jsxDEV5 } from "react/jsx-dev-runtime";
1708
1789
  function nextMessageId() {
1709
1790
  _msgIdCounter += 1;
1710
1791
  return `m-${Date.now().toString(36)}-${_msgIdCounter.toString(36)}`;
@@ -1853,6 +1934,14 @@ function REPLInk(props) {
1853
1934
  streamingIndexRef.current = -1;
1854
1935
  isFirstDeltaInStreamRef.current = true;
1855
1936
  },
1937
+ attachUsage: (usage) => {
1938
+ if (!usage)
1939
+ return;
1940
+ setMessages((msgs) => [
1941
+ ...msgs,
1942
+ withMessageId({ role: "usage", content: "", usage })
1943
+ ]);
1944
+ },
1856
1945
  setMode: (newMode) => {
1857
1946
  setMode(newMode);
1858
1947
  setStatusBarCtxState((ctx) => ({ ...ctx, mode: newMode }));
@@ -1974,33 +2063,33 @@ function REPLInk(props) {
1974
2063
  const tailKey = messages.length > 0 ? tailMsg?.id ?? `tail-${messages.length - 1}` : "tail-empty";
1975
2064
  const statusBarModel = shortModel(statusBarCtx.model);
1976
2065
  const statusBarWorkdir = statusBarCtx.cwd;
1977
- return /* @__PURE__ */ jsxDEV4(Box3, {
2066
+ return /* @__PURE__ */ jsxDEV5(Box3, {
1978
2067
  flexDirection: "column",
1979
2068
  children: [
1980
- staticItems.length > 0 ? /* @__PURE__ */ jsxDEV4(Static, {
2069
+ staticItems.length > 0 ? /* @__PURE__ */ jsxDEV5(Static, {
1981
2070
  items: staticItems,
1982
- children: (msg, i) => /* @__PURE__ */ jsxDEV4(MessageView, {
2071
+ children: (msg, i) => /* @__PURE__ */ jsxDEV5(MessageView, {
1983
2072
  msg,
1984
2073
  terminalWidth,
1985
2074
  tailBounds: staticTailBounds
1986
2075
  }, msg.id ?? `h-${msg.role}-${i}-${msg.content.length}`, false, undefined, this)
1987
2076
  }, undefined, false, undefined, this) : null,
1988
- tailMsg ? /* @__PURE__ */ jsxDEV4(MessageView, {
2077
+ tailMsg ? /* @__PURE__ */ jsxDEV5(MessageView, {
1989
2078
  msg: tailMsg,
1990
2079
  terminalWidth,
1991
2080
  tailBounds: dynamicTailBounds
1992
2081
  }, tailKey, false, undefined, this) : null,
1993
- /* @__PURE__ */ jsxDEV4(Box3, {
2082
+ /* @__PURE__ */ jsxDEV5(Box3, {
1994
2083
  flexDirection: "column",
1995
2084
  marginTop: 1,
1996
- children: /* @__PURE__ */ jsxDEV4(StatusBar, {
2085
+ children: /* @__PURE__ */ jsxDEV5(StatusBar, {
1997
2086
  model: statusBarModel,
1998
2087
  workdir: statusBarWorkdir
1999
2088
  }, undefined, false, undefined, this)
2000
2089
  }, undefined, false, undefined, this),
2001
- showPopup ? /* @__PURE__ */ jsxDEV4(Box3, {
2090
+ showPopup ? /* @__PURE__ */ jsxDEV5(Box3, {
2002
2091
  marginBottom: 0,
2003
- children: /* @__PURE__ */ jsxDEV4(SlashCommandPopup, {
2092
+ children: /* @__PURE__ */ jsxDEV5(SlashCommandPopup, {
2004
2093
  query: inputValue,
2005
2094
  selectedIndex: safePopupIndex,
2006
2095
  onIndexChange: handlePopupIndexChange,
@@ -2008,20 +2097,20 @@ function REPLInk(props) {
2008
2097
  onClose: handlePopupClose
2009
2098
  }, undefined, false, undefined, this)
2010
2099
  }, undefined, false, undefined, this) : null,
2011
- /* @__PURE__ */ jsxDEV4(Box3, {
2100
+ /* @__PURE__ */ jsxDEV5(Box3, {
2012
2101
  flexDirection: "column",
2013
2102
  children: [
2014
- /* @__PURE__ */ jsxDEV4(Text4, {
2103
+ /* @__PURE__ */ jsxDEV5(Text5, {
2015
2104
  children: COLORS.muted("─".repeat(40))
2016
2105
  }, undefined, false, undefined, this),
2017
- /* @__PURE__ */ jsxDEV4(MultilineInput, {
2106
+ /* @__PURE__ */ jsxDEV5(MultilineInput, {
2018
2107
  ref: multilineInputRef,
2019
2108
  onSubmit: handleSubmit,
2020
2109
  onEscape: handleEscapeCascade,
2021
2110
  popupActive: showPopup,
2022
2111
  onChange: setInputValue
2023
2112
  }, undefined, false, undefined, this),
2024
- /* @__PURE__ */ jsxDEV4(Text4, {
2113
+ /* @__PURE__ */ jsxDEV5(Text5, {
2025
2114
  children: COLORS.muted("─".repeat(40))
2026
2115
  }, undefined, false, undefined, this)
2027
2116
  ]
@@ -2048,17 +2137,18 @@ var init_repl_ink = __esm(() => {
2048
2137
  init_slash_command_popup();
2049
2138
  init_Markdown();
2050
2139
  init_StatusBar();
2140
+ init_UsageFooter();
2051
2141
  init_dynamic_tail();
2052
2142
  MarkdownBounded = React3.memo(({ text, bounds, width }) => {
2053
2143
  const out = renderBoundedAssistant(text, bounds, width);
2054
2144
  if (!out) {
2055
2145
  return null;
2056
2146
  }
2057
- return /* @__PURE__ */ jsxDEV4(Text4, {
2147
+ return /* @__PURE__ */ jsxDEV5(Text5, {
2058
2148
  children: out
2059
2149
  }, undefined, false, undefined, this);
2060
2150
  });
2061
- inkLogger = createLogger5("repl-ink");
2151
+ inkLogger = createLogger6("repl-ink");
2062
2152
  MultilineInput = React3.memo(React3.forwardRef(function MultilineInput2(props, ref) {
2063
2153
  const { onSubmit, placeholder, popupActive, onChange, onEscape } = props;
2064
2154
  const [buffer, setBuffer] = React3.useState("");
@@ -2256,12 +2346,12 @@ var init_repl_ink = __esm(() => {
2256
2346
  }
2257
2347
  }
2258
2348
  const showPlaceholder = buffer.length === 0 && placeholder;
2259
- return /* @__PURE__ */ jsxDEV4(Box3, {
2349
+ return /* @__PURE__ */ jsxDEV5(Box3, {
2260
2350
  flexDirection: "column",
2261
2351
  children: lines.map((line, i) => {
2262
2352
  const isFirst = i === 0;
2263
2353
  const isCursorRow = i === cursorRow;
2264
- const prefixEl = /* @__PURE__ */ jsxDEV4(Text4, {
2354
+ const prefixEl = /* @__PURE__ */ jsxDEV5(Text5, {
2265
2355
  color: isFirst ? "red" : "gray",
2266
2356
  children: isFirst ? "❯ " : "... "
2267
2357
  }, undefined, false, undefined, this);
@@ -2276,29 +2366,29 @@ var init_repl_ink = __esm(() => {
2276
2366
  atCursor = line[cursorCol] ?? " ";
2277
2367
  afterCursor = line.slice(cursorCol + 1);
2278
2368
  }
2279
- return /* @__PURE__ */ jsxDEV4(Box3, {
2369
+ return /* @__PURE__ */ jsxDEV5(Box3, {
2280
2370
  children: [
2281
2371
  prefixEl,
2282
- /* @__PURE__ */ jsxDEV4(Text4, {
2372
+ /* @__PURE__ */ jsxDEV5(Text5, {
2283
2373
  children: beforeCursor
2284
2374
  }, undefined, false, undefined, this),
2285
- /* @__PURE__ */ jsxDEV4(Text4, {
2375
+ /* @__PURE__ */ jsxDEV5(Text5, {
2286
2376
  inverse: !showPlaceholder,
2287
2377
  children: atCursor
2288
2378
  }, undefined, false, undefined, this),
2289
- showPlaceholder ? /* @__PURE__ */ jsxDEV4(Text4, {
2379
+ showPlaceholder ? /* @__PURE__ */ jsxDEV5(Text5, {
2290
2380
  color: "gray",
2291
2381
  children: placeholder?.slice(1) ?? ""
2292
- }, undefined, false, undefined, this) : /* @__PURE__ */ jsxDEV4(Text4, {
2382
+ }, undefined, false, undefined, this) : /* @__PURE__ */ jsxDEV5(Text5, {
2293
2383
  children: afterCursor
2294
2384
  }, undefined, false, undefined, this)
2295
2385
  ]
2296
2386
  }, i, true, undefined, this);
2297
2387
  }
2298
- return /* @__PURE__ */ jsxDEV4(Box3, {
2388
+ return /* @__PURE__ */ jsxDEV5(Box3, {
2299
2389
  children: [
2300
2390
  prefixEl,
2301
- /* @__PURE__ */ jsxDEV4(Text4, {
2391
+ /* @__PURE__ */ jsxDEV5(Text5, {
2302
2392
  children: line
2303
2393
  }, undefined, false, undefined, this)
2304
2394
  ]
@@ -2312,13 +2402,22 @@ var init_repl_ink = __esm(() => {
2312
2402
  tailBounds
2313
2403
  }) {
2314
2404
  if (msg.kind === "welcome") {
2315
- return /* @__PURE__ */ jsxDEV4(Box3, {
2405
+ return /* @__PURE__ */ jsxDEV5(Box3, {
2316
2406
  flexDirection: "column",
2317
- children: /* @__PURE__ */ jsxDEV4(Text4, {
2407
+ children: /* @__PURE__ */ jsxDEV5(Text5, {
2318
2408
  children: msg.content
2319
2409
  }, undefined, false, undefined, this)
2320
2410
  }, undefined, false, undefined, this);
2321
2411
  }
2412
+ if (msg.role === "usage") {
2413
+ return /* @__PURE__ */ jsxDEV5(Box3, {
2414
+ flexDirection: "column",
2415
+ marginLeft: 2,
2416
+ children: /* @__PURE__ */ jsxDEV5(UsageFooter, {
2417
+ usage: msg.usage
2418
+ }, undefined, false, undefined, this)
2419
+ }, undefined, false, undefined, this);
2420
+ }
2322
2421
  let prefix;
2323
2422
  let color;
2324
2423
  let contentColor;
@@ -2358,18 +2457,18 @@ var init_repl_ink = __esm(() => {
2358
2457
  const displayContent = msg.role === "tool" || msg.role === "tool_result" ? `${msg.toolName ?? "tool"}: ${msg.content}` : msg.content;
2359
2458
  const width = terminalWidth ?? process.stdout.columns ?? 80;
2360
2459
  if (msg.role === "assistant") {
2361
- const body = tailBounds ? /* @__PURE__ */ jsxDEV4(MarkdownBounded, {
2460
+ const body = tailBounds ? /* @__PURE__ */ jsxDEV5(MarkdownBounded, {
2362
2461
  text: displayContent,
2363
2462
  bounds: tailBounds,
2364
2463
  width
2365
- }, undefined, false, undefined, this) : /* @__PURE__ */ jsxDEV4(Markdown, {
2464
+ }, undefined, false, undefined, this) : /* @__PURE__ */ jsxDEV5(Markdown, {
2366
2465
  text: displayContent
2367
2466
  }, undefined, false, undefined, this);
2368
- return /* @__PURE__ */ jsxDEV4(Box3, {
2467
+ return /* @__PURE__ */ jsxDEV5(Box3, {
2369
2468
  flexDirection: "column",
2370
2469
  children: [
2371
- /* @__PURE__ */ jsxDEV4(Box3, {
2372
- children: /* @__PURE__ */ jsxDEV4(Text4, {
2470
+ /* @__PURE__ */ jsxDEV5(Box3, {
2471
+ children: /* @__PURE__ */ jsxDEV5(Text5, {
2373
2472
  color: "green",
2374
2473
  bold: true,
2375
2474
  children: [
@@ -2378,12 +2477,12 @@ var init_repl_ink = __esm(() => {
2378
2477
  ]
2379
2478
  }, undefined, true, undefined, this)
2380
2479
  }, undefined, false, undefined, this),
2381
- /* @__PURE__ */ jsxDEV4(Box3, {
2480
+ /* @__PURE__ */ jsxDEV5(Box3, {
2382
2481
  flexDirection: "column",
2383
2482
  marginLeft: 2,
2384
2483
  children: [
2385
2484
  body,
2386
- msg.streaming ? /* @__PURE__ */ jsxDEV4(Text4, {
2485
+ msg.streaming ? /* @__PURE__ */ jsxDEV5(Text5, {
2387
2486
  color: "gray",
2388
2487
  children: "▌"
2389
2488
  }, undefined, false, undefined, this) : null
@@ -2393,9 +2492,9 @@ var init_repl_ink = __esm(() => {
2393
2492
  }, undefined, true, undefined, this);
2394
2493
  }
2395
2494
  if (tailBounds) {
2396
- return /* @__PURE__ */ jsxDEV4(Box3, {
2495
+ return /* @__PURE__ */ jsxDEV5(Box3, {
2397
2496
  children: [
2398
- /* @__PURE__ */ jsxDEV4(Text4, {
2497
+ /* @__PURE__ */ jsxDEV5(Text5, {
2399
2498
  color,
2400
2499
  bold: true,
2401
2500
  children: [
@@ -2403,7 +2502,7 @@ var init_repl_ink = __esm(() => {
2403
2502
  " "
2404
2503
  ]
2405
2504
  }, undefined, true, undefined, this),
2406
- /* @__PURE__ */ jsxDEV4(Text4, {
2505
+ /* @__PURE__ */ jsxDEV5(Text5, {
2407
2506
  color: contentColor,
2408
2507
  children: [
2409
2508
  renderBoundedPlain(displayContent, tailBounds, width),
@@ -2413,9 +2512,9 @@ var init_repl_ink = __esm(() => {
2413
2512
  ]
2414
2513
  }, undefined, true, undefined, this);
2415
2514
  }
2416
- return /* @__PURE__ */ jsxDEV4(Box3, {
2515
+ return /* @__PURE__ */ jsxDEV5(Box3, {
2417
2516
  children: [
2418
- /* @__PURE__ */ jsxDEV4(Text4, {
2517
+ /* @__PURE__ */ jsxDEV5(Text5, {
2419
2518
  color,
2420
2519
  bold: true,
2421
2520
  children: [
@@ -2423,7 +2522,7 @@ var init_repl_ink = __esm(() => {
2423
2522
  " "
2424
2523
  ]
2425
2524
  }, undefined, true, undefined, this),
2426
- /* @__PURE__ */ jsxDEV4(Text4, {
2525
+ /* @__PURE__ */ jsxDEV5(Text5, {
2427
2526
  color: contentColor,
2428
2527
  children: [
2429
2528
  displayContent,
@@ -9486,7 +9585,7 @@ class CliQuietModeService {
9486
9585
  // package.json
9487
9586
  var package_default = {
9488
9587
  name: "@ai-setting/roy-agent-cli",
9489
- version: "1.7.52",
9588
+ version: "1.7.55",
9490
9589
  type: "module",
9491
9590
  description: "CLI for roy-agent - Non-interactive command execution",
9492
9591
  main: "./dist/index.js",
@@ -11108,6 +11207,23 @@ class SessionManager {
11108
11207
  // src/commands/shared/query-executor.ts
11109
11208
  init_stream_output_service();
11110
11209
  import { ContextError as ContextError3, ErrorCodes as ErrorCodes3, TracedAs as TracedAs4 } from "@ai-setting/roy-agent-core";
11210
+ import { notifyReactFailure } from "@ai-setting/roy-agent-core/env/error-reply";
11211
+ import { createLogger as createLogger5 } from "@ai-setting/roy-agent-core/env/log-trace/logger";
11212
+ var logger5 = createLogger5("cli:query-executor");
11213
+ function isAskUserError(error) {
11214
+ if (!error || typeof error !== "object")
11215
+ return false;
11216
+ const e = error;
11217
+ if (e.name === "AskUserError")
11218
+ return true;
11219
+ if (e.type === "ask-user")
11220
+ return true;
11221
+ if (typeof e.message === "string" && e.message.includes("__ASK_USER_ERROR__:")) {
11222
+ return true;
11223
+ }
11224
+ return false;
11225
+ }
11226
+
11111
11227
  class QueryExecutor {
11112
11228
  env;
11113
11229
  sessionComponent;
@@ -11123,12 +11239,14 @@ class QueryExecutor {
11123
11239
  };
11124
11240
  _pendingContextConfig = null;
11125
11241
  _pendingChunkCallback = undefined;
11242
+ failureNotifier;
11126
11243
  constructor(options) {
11127
11244
  this.env = options.env;
11128
11245
  this.sessionComponent = options.sessionComponent;
11129
11246
  this.output = options.output;
11130
11247
  this.quiet = options.quiet;
11131
11248
  this.silent = options.silent ?? false;
11249
+ this.failureNotifier = options.failureNotifier ?? notifyReactFailure;
11132
11250
  }
11133
11251
  initSummaryAgent(llmComponent, promptComponent) {
11134
11252
  const session = this.sessionComponent;
@@ -11232,6 +11350,7 @@ class QueryExecutor {
11232
11350
  try {
11233
11351
  result = await contextHandler.handleQueryWithContext(message, context);
11234
11352
  } catch (error) {
11353
+ await this.replyOnReactFailure(error, message, context);
11235
11354
  if (error instanceof ContextError3 && error.code === ErrorCodes3.CONTEXT_THRESHOLD_EXCEEDED) {
11236
11355
  this.output.error(`上下文阈值超出限制: ${error.usage?.totalTokens}/${error.contextWindow}`);
11237
11356
  this.output.info("请手动压缩会话: roy-agent sessions compact " + sessionId);
@@ -11243,6 +11362,23 @@ class QueryExecutor {
11243
11362
  }
11244
11363
  return result;
11245
11364
  }
11365
+ async replyOnReactFailure(error, query, context) {
11366
+ try {
11367
+ if (isAskUserError(error))
11368
+ return;
11369
+ const envEvent = context?.envEvent ?? context?.metadata?.envEvent;
11370
+ await this.failureNotifier({
11371
+ error,
11372
+ envEvent,
11373
+ query: typeof query === "string" ? query : undefined,
11374
+ sessionId: context?.sessionId
11375
+ });
11376
+ } catch (notifyErr) {
11377
+ logger5.warn("[#4418] React 中断兜底回复失败(已忽略)", {
11378
+ error: String(notifyErr)
11379
+ });
11380
+ }
11381
+ }
11246
11382
  dispose() {
11247
11383
  if (this.unsubscribe) {
11248
11384
  this.unsubscribe();
@@ -11560,6 +11696,7 @@ import {
11560
11696
 
11561
11697
  // src/commands/shared/event-handler.ts
11562
11698
  import { TracedAs as TracedAs5 } from "@ai-setting/roy-agent-core";
11699
+ import { notifyReactFailure as notifyReactFailure2 } from "@ai-setting/roy-agent-core/env/error-reply";
11563
11700
  function parseLarkPerTaskChatId(contextJson) {
11564
11701
  if (!contextJson)
11565
11702
  return;
@@ -11821,6 +11958,15 @@ class EventHandler {
11821
11958
  await this.onEvent(item.message);
11822
11959
  } catch (error) {
11823
11960
  console.error(`[EventHandler] 处理事件失败:`, error);
11961
+ try {
11962
+ await notifyReactFailure2({
11963
+ error,
11964
+ envEvent: item.envEvent,
11965
+ sessionId
11966
+ });
11967
+ } catch (notifyErr) {
11968
+ console.error(`[EventHandler] 失败说明回飞书异常:`, notifyErr);
11969
+ }
11824
11970
  }
11825
11971
  }
11826
11972
  this.isProcessing = false;
@@ -12075,7 +12221,7 @@ function hasActiveDescendants(env, parentSessionId) {
12075
12221
 
12076
12222
  // src/commands/interactive.ts
12077
12223
  init_stream_output_service();
12078
- import { createLogger as createLogger6, getTracerProvider as getTracerProvider3 } from "@ai-setting/roy-agent-core";
12224
+ import { createLogger as createLogger7, getTracerProvider as getTracerProvider3 } from "@ai-setting/roy-agent-core";
12079
12225
 
12080
12226
  // src/commands/input-handler.ts
12081
12227
  class InputHandler {
@@ -12400,7 +12546,7 @@ async function shutdownInteractiveSteps(deps) {
12400
12546
  envService,
12401
12547
  shouldDisposeEnvService,
12402
12548
  output,
12403
- logger: logger5,
12549
+ logger: logger6,
12404
12550
  console: consoleLike
12405
12551
  } = deps;
12406
12552
  const log = (msg) => consoleLike?.log?.(msg) ?? console.log(msg);
@@ -12413,7 +12559,7 @@ async function shutdownInteractiveSteps(deps) {
12413
12559
  await memoryPluginDispose();
12414
12560
  log("\uD83D\uDCBE 记忆已保存完毕");
12415
12561
  } catch (err) {
12416
- logger5.warn(`[Shutdown] MemoryPlugin dispose failed: ${err}`);
12562
+ logger6.warn(`[Shutdown] MemoryPlugin dispose failed: ${err}`);
12417
12563
  output.warn(`⚠️ 记忆保存失败: ${err}`);
12418
12564
  }
12419
12565
  }
@@ -12421,25 +12567,25 @@ async function shutdownInteractiveSteps(deps) {
12421
12567
  try {
12422
12568
  await lspManagerDispose();
12423
12569
  } catch (err) {
12424
- logger5.warn(`[Shutdown] LSP Manager dispose failed: ${err}`);
12570
+ logger6.warn(`[Shutdown] LSP Manager dispose failed: ${err}`);
12425
12571
  }
12426
12572
  }
12427
12573
  if (pluginAdapterDispose) {
12428
12574
  try {
12429
12575
  await pluginAdapterDispose();
12430
12576
  } catch (err) {
12431
- logger5.warn(`[Shutdown] PluginAdapter dispose failed: ${err}`);
12577
+ logger6.warn(`[Shutdown] PluginAdapter dispose failed: ${err}`);
12432
12578
  }
12433
12579
  }
12434
12580
  try {
12435
12581
  eventHandler.stop();
12436
12582
  } catch (err) {
12437
- logger5.warn(`[Shutdown] EventHandler.stop failed: ${err}`);
12583
+ logger6.warn(`[Shutdown] EventHandler.stop failed: ${err}`);
12438
12584
  }
12439
12585
  try {
12440
12586
  queryExecutor.dispose();
12441
12587
  } catch (err) {
12442
- logger5.warn(`[Shutdown] QueryExecutor.dispose failed: ${err}`);
12588
+ logger6.warn(`[Shutdown] QueryExecutor.dispose failed: ${err}`);
12443
12589
  }
12444
12590
  try {
12445
12591
  const eventSourceComponent = env.getComponent("event-source");
@@ -12448,7 +12594,7 @@ async function shutdownInteractiveSteps(deps) {
12448
12594
  if (sources && sources.length > 0) {
12449
12595
  for (const source of sources) {
12450
12596
  const status = eventSourceComponent.getStatus?.(source.id);
12451
- logger5.debug(`[Shutdown] 检查事件源: ${source.id}, 状态: ${status}`);
12597
+ logger6.debug(`[Shutdown] 检查事件源: ${source.id}, 状态: ${status}`);
12452
12598
  if (status !== "stopped" && status !== "created") {
12453
12599
  try {
12454
12600
  await eventSourceComponent.stopSource(source.id);
@@ -12461,20 +12607,20 @@ async function shutdownInteractiveSteps(deps) {
12461
12607
  }
12462
12608
  }
12463
12609
  } catch (err) {
12464
- logger5.warn(`[Shutdown] EventSource cleanup failed: ${err}`);
12610
+ logger6.warn(`[Shutdown] EventSource cleanup failed: ${err}`);
12465
12611
  }
12466
12612
  if (shouldDisposeEnvService) {
12467
12613
  try {
12468
12614
  await envService.dispose();
12469
12615
  } catch (err) {
12470
- logger5.warn(`[Shutdown] envService.dispose failed: ${err}`);
12616
+ logger6.warn(`[Shutdown] envService.dispose failed: ${err}`);
12471
12617
  }
12472
12618
  }
12473
12619
  try {
12474
12620
  await TempFileManager2.getInstance().cleanupAll();
12475
- logger5.debug("[Shutdown] TempFileManager cleaned up");
12621
+ logger6.debug("[Shutdown] TempFileManager cleaned up");
12476
12622
  } catch (err) {
12477
- logger5.warn(`[Shutdown] TempFileManager cleanup failed: ${err}`);
12623
+ logger6.warn(`[Shutdown] TempFileManager cleanup failed: ${err}`);
12478
12624
  }
12479
12625
  }
12480
12626
 
@@ -12483,7 +12629,7 @@ var DEFAULT_LOGGER = {
12483
12629
  warn: (msg) => console.error(`[shutdown] ${msg}`)
12484
12630
  };
12485
12631
  function registerShutdownHandlers(cleanup, options = {}) {
12486
- const logger5 = options.logger ?? DEFAULT_LOGGER;
12632
+ const logger6 = options.logger ?? DEFAULT_LOGGER;
12487
12633
  const exitCode = options.exitCode ?? 0;
12488
12634
  const shutdownMessage = options.shutdownMessage ?? "Cleaning up...";
12489
12635
  let shuttingDown = false;
@@ -12493,7 +12639,7 @@ function registerShutdownHandlers(cleanup, options = {}) {
12493
12639
  }
12494
12640
  shuttingDown = true;
12495
12641
  try {
12496
- logger5.debug?.(`[${signal}] ${shutdownMessage}`);
12642
+ logger6.debug?.(`[${signal}] ${shutdownMessage}`);
12497
12643
  } catch {}
12498
12644
  if (cleanup) {
12499
12645
  try {
@@ -12501,7 +12647,7 @@ function registerShutdownHandlers(cleanup, options = {}) {
12501
12647
  } catch (err) {
12502
12648
  const message = err instanceof Error ? err.message : String(err);
12503
12649
  try {
12504
- logger5.warn(`Cleanup failed: ${message}`);
12650
+ logger6.warn(`Cleanup failed: ${message}`);
12505
12651
  } catch {
12506
12652
  try {
12507
12653
  process.stderr.write(`[shutdown] Cleanup failed: ${message}
@@ -12526,6 +12672,7 @@ function registerShutdownHandlers(cleanup, options = {}) {
12526
12672
 
12527
12673
  // src/commands/interactive.ts
12528
12674
  import { AskUserError, cascadeStopSessionTree } from "@ai-setting/roy-agent-core";
12675
+ import { notifyReactFailure as notifyReactFailure3 } from "@ai-setting/roy-agent-core/env/error-reply";
12529
12676
 
12530
12677
  // src/commands/shared/usage-formatter.ts
12531
12678
  import chalk4 from "chalk";
@@ -12549,8 +12696,10 @@ function formatCacheLine(usage, options = {}) {
12549
12696
  return chalk4.gray("\uD83D\uDCB0 Cache: read 0 / write 0 (saved ~$0.000 / 0% hit rate)");
12550
12697
  }
12551
12698
  const saved = cacheRead * (PRICING.regularPerToken - PRICING.cacheReadPerToken);
12552
- const denom = cacheRead + cacheWrite;
12553
- const hitRate = denom > 0 ? (cacheRead / denom * 100).toFixed(0) : "0";
12699
+ const promptTokens = usage.promptTokens ?? 0;
12700
+ const denom = usage.promptTokensTotal !== undefined && usage.promptTokensTotal > 0 ? usage.promptTokensTotal : promptTokens + cacheRead + cacheWrite;
12701
+ const rawRate = denom > 0 ? cacheRead / denom * 100 : 0;
12702
+ const hitRate = Math.min(100, Math.max(0, rawRate)).toFixed(0);
12554
12703
  const text = `\uD83D\uDCB0 Cache: read ${cacheRead} / write ${cacheWrite} (saved ~$${saved.toFixed(3)} / ${hitRate}% hit rate)`;
12555
12704
  if (!colorize)
12556
12705
  return text;
@@ -12710,7 +12859,7 @@ var COLOR_BY_STATUS = {
12710
12859
  busy: COLORS.accent
12711
12860
  };
12712
12861
  // src/commands/interactive.ts
12713
- var logger5 = createLogger6("cli:interactive");
12862
+ var logger6 = createLogger7("cli:interactive");
12714
12863
  var USER_PROMPT = COLORS.accent("❯ ");
12715
12864
 
12716
12865
  class REPL {
@@ -12902,10 +13051,10 @@ ${COLORS.system("[正在中断...]")}`);
12902
13051
  const agentComponent = this.env.getComponent("agent");
12903
13052
  if (agentComponent?.abortAll) {
12904
13053
  agentComponent.abortAll();
12905
- logger5.debug("[/abort] AgentComponent.abortAll() called");
13054
+ logger6.debug("[/abort] AgentComponent.abortAll() called");
12906
13055
  } else if (agentComponent?.abort) {
12907
13056
  agentComponent.abort("default");
12908
- logger5.debug("[/abort] AgentComponent.abort('default') called (fallback)");
13057
+ logger6.debug("[/abort] AgentComponent.abort('default') called (fallback)");
12909
13058
  }
12910
13059
  }
12911
13060
  abortStream();
@@ -12953,6 +13102,9 @@ ${COLORS.system("[没有正在执行的请求]")}`);
12953
13102
  break;
12954
13103
  case "llm.completed":
12955
13104
  inkApiRef.finishAssistantStream();
13105
+ if (event.usage) {
13106
+ inkApiRef.attachUsage(event.usage);
13107
+ }
12956
13108
  break;
12957
13109
  case "tool.call":
12958
13110
  inkApiRef.appendMessage({
@@ -13000,7 +13152,7 @@ ${COLORS.system("[没有正在执行的请求]")}`);
13000
13152
  try {
13001
13153
  await this.options.onAfterExecute(this.lastUsage);
13002
13154
  } catch (hookError) {
13003
- logger5.warn("onAfterExecute hook failed (ink path)", { error: String(hookError) });
13155
+ logger6.warn("onAfterExecute hook failed (ink path)", { error: String(hookError) });
13004
13156
  }
13005
13157
  }
13006
13158
  } catch (err) {
@@ -13020,7 +13172,7 @@ ${COLORS.system("[没有正在执行的请求]")}`);
13020
13172
  await this.shutdown();
13021
13173
  resolve();
13022
13174
  }).catch(async (err) => {
13023
- logger5.error("ink REPL render failed", { error: String(err) });
13175
+ logger6.error("ink REPL render failed", { error: String(err) });
13024
13176
  await this.shutdown();
13025
13177
  resolve();
13026
13178
  });
@@ -13077,7 +13229,7 @@ ${COLORS.system("[没有正在执行的请求]")}`);
13077
13229
  }
13078
13230
  if (key.name === "escape") {
13079
13231
  this.handleEscKey().catch((err) => {
13080
- logger5.warn(`[Esc] handleEscKey failed: ${err}`);
13232
+ logger6.warn(`[Esc] handleEscKey failed: ${err}`);
13081
13233
  });
13082
13234
  }
13083
13235
  };
@@ -13309,7 +13461,7 @@ ${COLORS.system("[通知2] ❯ " + message.replace(/\n/g, `
13309
13461
  try {
13310
13462
  await this.options.onAfterExecute(this.lastUsage);
13311
13463
  } catch (hookError) {
13312
- logger5.warn("onAfterExecute hook failed", { error: String(hookError) });
13464
+ logger6.warn("onAfterExecute hook failed", { error: String(hookError) });
13313
13465
  }
13314
13466
  }
13315
13467
  } catch (error) {
@@ -13321,6 +13473,15 @@ ${COLORS.system("[通知2] ❯ " + message.replace(/\n/g, `
13321
13473
  ${COLORS.error("❌ 执行失败: " + error)}
13322
13474
  `);
13323
13475
  rootSpan.setAttribute("error", String(error));
13476
+ try {
13477
+ await notifyReactFailure3({
13478
+ error,
13479
+ envEvent: agentContext?.envEvent,
13480
+ sessionId: agentContext?.eventSessionId
13481
+ });
13482
+ } catch (notifyErr) {
13483
+ console.error(`[#4418] 失败说明回飞书异常:`, notifyErr);
13484
+ }
13324
13485
  }
13325
13486
  } finally {
13326
13487
  rootSpan.end();
@@ -13472,12 +13633,12 @@ ${COLORS.error("❌ 执行失败: " + error)}
13472
13633
  }
13473
13634
  async handleEscKey() {
13474
13635
  if (this.isShuttingDown) {
13475
- logger5.debug("[Esc] handler entered (isShuttingDown=true) → early return");
13636
+ logger6.debug("[Esc] handler entered (isShuttingDown=true) → early return");
13476
13637
  return;
13477
13638
  }
13478
13639
  const mainSessionIdEarly = this.options.sessionId;
13479
13640
  const hasActiveDescendant = mainSessionIdEarly ? hasActiveDescendants(this.env, mainSessionIdEarly) : false;
13480
- logger5.debug(`[Esc] handler entered: isExecuting=${this.isExecuting} hasActiveDescendant=${hasActiveDescendant} lastEscPressTime=${this.lastEscPressTime}`);
13641
+ logger6.debug(`[Esc] handler entered: isExecuting=${this.isExecuting} hasActiveDescendant=${hasActiveDescendant} lastEscPressTime=${this.lastEscPressTime}`);
13481
13642
  const now = Date.now();
13482
13643
  const isExecuting = this.isExecuting;
13483
13644
  const canCascade = isExecuting || hasActiveDescendant;
@@ -13486,17 +13647,17 @@ ${COLORS.error("❌ 执行失败: " + error)}
13486
13647
  this.lastEscPressTime = now;
13487
13648
  }
13488
13649
  if (!canCascade) {
13489
- logger5.debug("[Esc] idle no-op: isExecuting=false, no active descendant");
13650
+ logger6.debug("[Esc] idle no-op: isExecuting=false, no active descendant");
13490
13651
  return;
13491
13652
  }
13492
13653
  if (this.env) {
13493
13654
  const agentComponent = this.env.getComponent("agent");
13494
13655
  if (agentComponent?.abortAll) {
13495
13656
  agentComponent.abortAll();
13496
- logger5.debug("[Esc] AgentComponent.abortAll() called");
13657
+ logger6.debug("[Esc] AgentComponent.abortAll() called");
13497
13658
  } else if (agentComponent?.abort) {
13498
13659
  agentComponent.abort("default");
13499
- logger5.debug("[Esc] AgentComponent.abort('default') called (fallback)");
13660
+ logger6.debug("[Esc] AgentComponent.abort('default') called (fallback)");
13500
13661
  }
13501
13662
  }
13502
13663
  abortStream();
@@ -13504,7 +13665,7 @@ ${COLORS.error("❌ 执行失败: " + error)}
13504
13665
  try {
13505
13666
  this.inkApi.finishAssistantStream();
13506
13667
  } catch (err) {
13507
- logger5.warn("[Esc] finishAssistantStream failed", { error: String(err) });
13668
+ logger6.warn("[Esc] finishAssistantStream failed", { error: String(err) });
13508
13669
  }
13509
13670
  }
13510
13671
  const mainSessionId = this.options.sessionId;
@@ -13517,9 +13678,9 @@ ${COLORS.error("❌ 执行失败: " + error)}
13517
13678
  timer = setTimeout(() => reject(new Error("cascade timeout 5s")), 5000);
13518
13679
  })
13519
13680
  ]);
13520
- logger5.info(`[Esc] cascade stop force=${result.force} tasks=${result.stoppedTaskIds.length} workflows=${result.stoppedWorkflowSessionIds.length} errors=${result.errors.length}`);
13681
+ logger6.info(`[Esc] cascade stop force=${result.force} tasks=${result.stoppedTaskIds.length} workflows=${result.stoppedWorkflowSessionIds.length} errors=${result.errors.length}`);
13521
13682
  } catch (err) {
13522
- logger5.warn(`[Esc] cascade stop failed: ${err}`);
13683
+ logger6.warn(`[Esc] cascade stop failed: ${err}`);
13523
13684
  } finally {
13524
13685
  if (timer !== undefined)
13525
13686
  clearTimeout(timer);
@@ -13858,6 +14019,8 @@ function createInteractiveCommand(externalEnvService) {
13858
14019
  return result;
13859
14020
  },
13860
14021
  onAfterExecute: (usage) => {
14022
+ if (useInk)
14023
+ return;
13861
14024
  if (usage) {
13862
14025
  const line = formatCacheLine(usage);
13863
14026
  if (line) {
@@ -13901,7 +14064,7 @@ function createInteractiveCommand(externalEnvService) {
13901
14064
  envService,
13902
14065
  shouldDisposeEnvService,
13903
14066
  output,
13904
- logger: logger5,
14067
+ logger: logger6,
13905
14068
  console
13906
14069
  });
13907
14070
  }
@@ -13918,7 +14081,7 @@ function createInteractiveCommand(externalEnvService) {
13918
14081
  envService,
13919
14082
  shouldDisposeEnvService,
13920
14083
  output,
13921
- logger: logger5,
14084
+ logger: logger6,
13922
14085
  console
13923
14086
  });
13924
14087
  });
@@ -14010,17 +14173,17 @@ import { render } from "ink";
14010
14173
 
14011
14174
  // src/RunTui.tsx
14012
14175
  import { useState as useState6, useCallback as useCallback4, useRef as useRef3, useEffect as useEffect5 } from "react";
14013
- import { Box as Box7, Text as Text9, useApp as useApp2, useStdout } from "ink";
14176
+ import { Box as Box7, Text as Text10, useApp as useApp2, useStdout } from "ink";
14014
14177
 
14015
14178
  // src/tui/App.tsx
14016
14179
  init_Markdown();
14017
- import { Box as Box5, Static as Static2, Text as Text7 } from "ink";
14180
+ import { Box as Box5, Static as Static2, Text as Text8 } from "ink";
14018
14181
 
14019
14182
  // src/tui/Spinner.tsx
14020
14183
  init_theme();
14021
14184
  import { useEffect as useEffect3, useState as useState3 } from "react";
14022
- import { Text as Text5 } from "ink";
14023
- import { jsxDEV as jsxDEV5 } from "react/jsx-dev-runtime";
14185
+ import { Text as Text6 } from "ink";
14186
+ import { jsxDEV as jsxDEV6 } from "react/jsx-dev-runtime";
14024
14187
  var FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
14025
14188
  var Spinner = ({
14026
14189
  label,
@@ -14034,14 +14197,14 @@ var Spinner = ({
14034
14197
  }, intervalMs);
14035
14198
  return () => clearInterval(timer);
14036
14199
  }, [intervalMs]);
14037
- return /* @__PURE__ */ jsxDEV5(Text5, {
14200
+ return /* @__PURE__ */ jsxDEV6(Text6, {
14038
14201
  children: [
14039
- /* @__PURE__ */ jsxDEV5(Text5, {
14202
+ /* @__PURE__ */ jsxDEV6(Text6, {
14040
14203
  color: theme.primary,
14041
14204
  children: FRAMES[frame]
14042
14205
  }, undefined, false, undefined, this),
14043
14206
  " ",
14044
- /* @__PURE__ */ jsxDEV5(Text5, {
14207
+ /* @__PURE__ */ jsxDEV6(Text6, {
14045
14208
  dimColor: true,
14046
14209
  children: label
14047
14210
  }, undefined, false, undefined, this)
@@ -14053,7 +14216,7 @@ var Spinner = ({
14053
14216
  init_StatusBar();
14054
14217
 
14055
14218
  // src/tui/ToolCall.tsx
14056
- import { Box as Box4, Text as Text6 } from "ink";
14219
+ import { Box as Box4, Text as Text7 } from "ink";
14057
14220
  init_theme();
14058
14221
 
14059
14222
  // src/tui/tool-call.ts
@@ -14211,17 +14374,17 @@ function formatDuration(ms) {
14211
14374
  }
14212
14375
 
14213
14376
  // src/tui/ToolCall.tsx
14214
- import { jsxDEV as jsxDEV6 } from "react/jsx-dev-runtime";
14377
+ import { jsxDEV as jsxDEV7 } from "react/jsx-dev-runtime";
14215
14378
  var StatusBadge = ({ info }) => {
14216
14379
  const theme = getTheme();
14217
14380
  if (info.status === "running") {
14218
- return /* @__PURE__ */ jsxDEV6(Spinner, {
14381
+ return /* @__PURE__ */ jsxDEV7(Spinner, {
14219
14382
  label: "running"
14220
14383
  }, undefined, false, undefined, this);
14221
14384
  }
14222
14385
  const duration = info.durationMs !== undefined ? ` ${formatDuration(info.durationMs)}` : "";
14223
14386
  if (info.status === "error") {
14224
- return /* @__PURE__ */ jsxDEV6(Text6, {
14387
+ return /* @__PURE__ */ jsxDEV7(Text7, {
14225
14388
  color: theme.error,
14226
14389
  children: [
14227
14390
  "✗",
@@ -14229,7 +14392,7 @@ var StatusBadge = ({ info }) => {
14229
14392
  ]
14230
14393
  }, undefined, true, undefined, this);
14231
14394
  }
14232
- return /* @__PURE__ */ jsxDEV6(Text6, {
14395
+ return /* @__PURE__ */ jsxDEV7(Text7, {
14233
14396
  color: theme.success,
14234
14397
  children: [
14235
14398
  "✓",
@@ -14247,42 +14410,42 @@ var ToolCall = ({
14247
14410
  `) : [];
14248
14411
  const errorLines = showOutput && info.error ? info.error.split(`
14249
14412
  `).slice(0, 12) : [];
14250
- return /* @__PURE__ */ jsxDEV6(Box4, {
14413
+ return /* @__PURE__ */ jsxDEV7(Box4, {
14251
14414
  flexDirection: "column",
14252
14415
  marginBottom: 1,
14253
14416
  children: [
14254
- /* @__PURE__ */ jsxDEV6(Box4, {
14417
+ /* @__PURE__ */ jsxDEV7(Box4, {
14255
14418
  children: [
14256
- /* @__PURE__ */ jsxDEV6(Text6, {
14419
+ /* @__PURE__ */ jsxDEV7(Text7, {
14257
14420
  color: theme.tool,
14258
14421
  children: [
14259
14422
  icon,
14260
14423
  " "
14261
14424
  ]
14262
14425
  }, undefined, true, undefined, this),
14263
- /* @__PURE__ */ jsxDEV6(Text6, {
14426
+ /* @__PURE__ */ jsxDEV7(Text7, {
14264
14427
  bold: true,
14265
14428
  color: theme.tool,
14266
14429
  children: label
14267
14430
  }, undefined, false, undefined, this),
14268
- /* @__PURE__ */ jsxDEV6(Text6, {
14431
+ /* @__PURE__ */ jsxDEV7(Text7, {
14269
14432
  children: " "
14270
14433
  }, undefined, false, undefined, this),
14271
- /* @__PURE__ */ jsxDEV6(StatusBadge, {
14434
+ /* @__PURE__ */ jsxDEV7(StatusBadge, {
14272
14435
  info
14273
14436
  }, undefined, false, undefined, this)
14274
14437
  ]
14275
14438
  }, undefined, true, undefined, this),
14276
- detail ? /* @__PURE__ */ jsxDEV6(Box4, {
14439
+ detail ? /* @__PURE__ */ jsxDEV7(Box4, {
14277
14440
  marginLeft: 2,
14278
- children: /* @__PURE__ */ jsxDEV6(Text6, {
14441
+ children: /* @__PURE__ */ jsxDEV7(Text7, {
14279
14442
  dimColor: true,
14280
14443
  children: detail
14281
14444
  }, undefined, false, undefined, this)
14282
14445
  }, undefined, false, undefined, this) : null,
14283
- outputLines.map((line, i) => /* @__PURE__ */ jsxDEV6(Box4, {
14446
+ outputLines.map((line, i) => /* @__PURE__ */ jsxDEV7(Box4, {
14284
14447
  marginLeft: 2,
14285
- children: /* @__PURE__ */ jsxDEV6(Text6, {
14448
+ children: /* @__PURE__ */ jsxDEV7(Text7, {
14286
14449
  dimColor: true,
14287
14450
  children: [
14288
14451
  i === 0 ? "└ " : " ",
@@ -14290,9 +14453,9 @@ var ToolCall = ({
14290
14453
  ]
14291
14454
  }, undefined, true, undefined, this)
14292
14455
  }, `out-${i}`, false, undefined, this)),
14293
- errorLines.map((line, i) => /* @__PURE__ */ jsxDEV6(Box4, {
14456
+ errorLines.map((line, i) => /* @__PURE__ */ jsxDEV7(Box4, {
14294
14457
  marginLeft: 2,
14295
- children: /* @__PURE__ */ jsxDEV6(Text6, {
14458
+ children: /* @__PURE__ */ jsxDEV7(Text7, {
14296
14459
  color: theme.error,
14297
14460
  children: [
14298
14461
  i === 0 ? "└ " : " ",
@@ -14306,16 +14469,18 @@ var ToolCall = ({
14306
14469
 
14307
14470
  // src/tui/App.tsx
14308
14471
  init_theme();
14309
- import { jsxDEV as jsxDEV7 } from "react/jsx-dev-runtime";
14472
+ init_UsageFooter();
14473
+ import { jsxDEV as jsxDEV8 } from "react/jsx-dev-runtime";
14310
14474
  var ROLE_PREFIX = {
14311
14475
  user: "\uD83D\uDC64 You",
14312
14476
  assistant: "\uD83E\uDD16 Agent",
14313
- tool: "\uD83D\uDD27 Tool"
14477
+ tool: "\uD83D\uDD27 Tool",
14478
+ usage: ""
14314
14479
  };
14315
14480
  var RoleLabel = ({ role }) => {
14316
14481
  const theme = getTheme();
14317
14482
  const roleColor = role === "user" ? theme.user : role === "assistant" ? theme.assistant : theme.tool;
14318
- return /* @__PURE__ */ jsxDEV7(Text7, {
14483
+ return /* @__PURE__ */ jsxDEV8(Text8, {
14319
14484
  bold: true,
14320
14485
  color: roleColor,
14321
14486
  children: ROLE_PREFIX[role]
@@ -14323,16 +14488,16 @@ var RoleLabel = ({ role }) => {
14323
14488
  };
14324
14489
  var HistoryEntry = ({ item }) => {
14325
14490
  if (item.role === "user") {
14326
- return /* @__PURE__ */ jsxDEV7(Box5, {
14491
+ return /* @__PURE__ */ jsxDEV8(Box5, {
14327
14492
  flexDirection: "column",
14328
14493
  marginBottom: 1,
14329
14494
  children: [
14330
- /* @__PURE__ */ jsxDEV7(RoleLabel, {
14495
+ /* @__PURE__ */ jsxDEV8(RoleLabel, {
14331
14496
  role: "user"
14332
14497
  }, undefined, false, undefined, this),
14333
- /* @__PURE__ */ jsxDEV7(Box5, {
14498
+ /* @__PURE__ */ jsxDEV8(Box5, {
14334
14499
  marginLeft: 2,
14335
- children: /* @__PURE__ */ jsxDEV7(Text7, {
14500
+ children: /* @__PURE__ */ jsxDEV8(Text8, {
14336
14501
  children: item.content
14337
14502
  }, undefined, false, undefined, this)
14338
14503
  }, undefined, false, undefined, this)
@@ -14341,20 +14506,20 @@ var HistoryEntry = ({ item }) => {
14341
14506
  }
14342
14507
  if (item.role === "tool") {
14343
14508
  if (item.tool) {
14344
- return /* @__PURE__ */ jsxDEV7(ToolCall, {
14509
+ return /* @__PURE__ */ jsxDEV8(ToolCall, {
14345
14510
  info: item.tool
14346
14511
  }, undefined, false, undefined, this);
14347
14512
  }
14348
- return /* @__PURE__ */ jsxDEV7(Box5, {
14513
+ return /* @__PURE__ */ jsxDEV8(Box5, {
14349
14514
  flexDirection: "column",
14350
14515
  marginBottom: 1,
14351
14516
  children: [
14352
- /* @__PURE__ */ jsxDEV7(RoleLabel, {
14517
+ /* @__PURE__ */ jsxDEV8(RoleLabel, {
14353
14518
  role: "tool"
14354
14519
  }, undefined, false, undefined, this),
14355
- /* @__PURE__ */ jsxDEV7(Box5, {
14520
+ /* @__PURE__ */ jsxDEV8(Box5, {
14356
14521
  marginLeft: 2,
14357
- children: /* @__PURE__ */ jsxDEV7(Text7, {
14522
+ children: /* @__PURE__ */ jsxDEV8(Text8, {
14358
14523
  dimColor: true,
14359
14524
  children: item.content
14360
14525
  }, undefined, false, undefined, this)
@@ -14362,17 +14527,22 @@ var HistoryEntry = ({ item }) => {
14362
14527
  ]
14363
14528
  }, undefined, true, undefined, this);
14364
14529
  }
14365
- return /* @__PURE__ */ jsxDEV7(Box5, {
14530
+ if (item.role === "usage") {
14531
+ return /* @__PURE__ */ jsxDEV8(UsageFooter, {
14532
+ usage: item.usage
14533
+ }, undefined, false, undefined, this);
14534
+ }
14535
+ return /* @__PURE__ */ jsxDEV8(Box5, {
14366
14536
  flexDirection: "column",
14367
14537
  marginBottom: 1,
14368
14538
  children: [
14369
- item.continuation ? null : /* @__PURE__ */ jsxDEV7(RoleLabel, {
14539
+ item.continuation ? null : /* @__PURE__ */ jsxDEV8(RoleLabel, {
14370
14540
  role: "assistant"
14371
14541
  }, undefined, false, undefined, this),
14372
- /* @__PURE__ */ jsxDEV7(Box5, {
14542
+ /* @__PURE__ */ jsxDEV8(Box5, {
14373
14543
  marginLeft: 2,
14374
14544
  flexDirection: "column",
14375
- children: /* @__PURE__ */ jsxDEV7(Markdown, {
14545
+ children: /* @__PURE__ */ jsxDEV8(Markdown, {
14376
14546
  text: item.content
14377
14547
  }, undefined, false, undefined, this)
14378
14548
  }, undefined, false, undefined, this)
@@ -14389,65 +14559,65 @@ var App = ({
14389
14559
  tokenCount
14390
14560
  }) => {
14391
14561
  const theme = getTheme();
14392
- return /* @__PURE__ */ jsxDEV7(Box5, {
14562
+ return /* @__PURE__ */ jsxDEV8(Box5, {
14393
14563
  flexDirection: "column",
14394
14564
  children: [
14395
- /* @__PURE__ */ jsxDEV7(Box5, {
14565
+ /* @__PURE__ */ jsxDEV8(Box5, {
14396
14566
  borderStyle: "round",
14397
14567
  borderColor: theme.primary,
14398
14568
  paddingX: 1,
14399
14569
  marginBottom: 1,
14400
14570
  children: [
14401
- /* @__PURE__ */ jsxDEV7(Text7, {
14571
+ /* @__PURE__ */ jsxDEV8(Text8, {
14402
14572
  bold: true,
14403
14573
  color: theme.primary,
14404
14574
  children: "\uD83D\uDE80 Roy Agent"
14405
14575
  }, undefined, false, undefined, this),
14406
- /* @__PURE__ */ jsxDEV7(Text7, {
14576
+ /* @__PURE__ */ jsxDEV8(Text8, {
14407
14577
  dimColor: true,
14408
14578
  children: " — interactive TUI"
14409
14579
  }, undefined, false, undefined, this)
14410
14580
  ]
14411
14581
  }, undefined, true, undefined, this),
14412
- /* @__PURE__ */ jsxDEV7(Static2, {
14582
+ /* @__PURE__ */ jsxDEV8(Static2, {
14413
14583
  items: history,
14414
- children: (item, index) => /* @__PURE__ */ jsxDEV7(HistoryEntry, {
14584
+ children: (item, index) => /* @__PURE__ */ jsxDEV8(HistoryEntry, {
14415
14585
  item
14416
14586
  }, item.id ?? `${item.role}:${index}`, false, undefined, this)
14417
14587
  }, undefined, false, undefined, this),
14418
- status === "thinking" ? /* @__PURE__ */ jsxDEV7(Box5, {
14588
+ status === "thinking" ? /* @__PURE__ */ jsxDEV8(Box5, {
14419
14589
  marginBottom: 1,
14420
- children: /* @__PURE__ */ jsxDEV7(Spinner, {
14590
+ children: /* @__PURE__ */ jsxDEV8(Spinner, {
14421
14591
  label: "Agent is thinking..."
14422
14592
  }, undefined, false, undefined, this)
14423
14593
  }, undefined, false, undefined, this) : null,
14424
- status === "streaming" && streamingText ? /* @__PURE__ */ jsxDEV7(Box5, {
14594
+ status === "streaming" && streamingText ? /* @__PURE__ */ jsxDEV8(Box5, {
14425
14595
  flexDirection: "column",
14426
14596
  marginBottom: 1,
14427
14597
  children: [
14428
- /* @__PURE__ */ jsxDEV7(RoleLabel, {
14598
+ /* @__PURE__ */ jsxDEV8(RoleLabel, {
14429
14599
  role: "assistant"
14430
14600
  }, undefined, false, undefined, this),
14431
- /* @__PURE__ */ jsxDEV7(Box5, {
14601
+ /* @__PURE__ */ jsxDEV8(Box5, {
14432
14602
  marginLeft: 2,
14433
- children: /* @__PURE__ */ jsxDEV7(Markdown, {
14603
+ children: /* @__PURE__ */ jsxDEV8(Markdown, {
14434
14604
  text: streamingText
14435
14605
  }, undefined, false, undefined, this)
14436
14606
  }, undefined, false, undefined, this)
14437
14607
  ]
14438
- }, undefined, true, undefined, this) : status === "streaming" ? /* @__PURE__ */ jsxDEV7(Box5, {
14608
+ }, undefined, true, undefined, this) : status === "streaming" ? /* @__PURE__ */ jsxDEV8(Box5, {
14439
14609
  marginBottom: 1,
14440
- children: /* @__PURE__ */ jsxDEV7(Spinner, {
14610
+ children: /* @__PURE__ */ jsxDEV8(Spinner, {
14441
14611
  label: "Generating response..."
14442
14612
  }, undefined, false, undefined, this)
14443
14613
  }, undefined, false, undefined, this) : null,
14444
- liveTools && liveTools.length > 0 ? /* @__PURE__ */ jsxDEV7(Box5, {
14614
+ liveTools && liveTools.length > 0 ? /* @__PURE__ */ jsxDEV8(Box5, {
14445
14615
  flexDirection: "column",
14446
- children: liveTools.map((tool, i) => /* @__PURE__ */ jsxDEV7(ToolCall, {
14616
+ children: liveTools.map((tool, i) => /* @__PURE__ */ jsxDEV8(ToolCall, {
14447
14617
  info: tool
14448
14618
  }, `live-tool-${i}`, false, undefined, this))
14449
14619
  }, undefined, false, undefined, this) : null,
14450
- /* @__PURE__ */ jsxDEV7(StatusBar, {
14620
+ /* @__PURE__ */ jsxDEV8(StatusBar, {
14451
14621
  model,
14452
14622
  workdir,
14453
14623
  tokenCount
@@ -14462,7 +14632,7 @@ init_StatusBar();
14462
14632
 
14463
14633
  // src/tui/TextInput.tsx
14464
14634
  import { useEffect as useEffect4, useReducer, useState as useState4 } from "react";
14465
- import { Box as Box6, Text as Text8, useInput as useInput3 } from "ink";
14635
+ import { Box as Box6, Text as Text9, useInput as useInput3 } from "ink";
14466
14636
 
14467
14637
  // src/tui/text-input-editing.ts
14468
14638
  function clampCursor(value, cursor) {
@@ -14533,7 +14703,7 @@ function editReducer(state, action) {
14533
14703
  }
14534
14704
 
14535
14705
  // src/tui/TextInput.tsx
14536
- import { jsxDEV as jsxDEV8, Fragment } from "react/jsx-dev-runtime";
14706
+ import { jsxDEV as jsxDEV9, Fragment } from "react/jsx-dev-runtime";
14537
14707
  var TextInput = ({
14538
14708
  label,
14539
14709
  prefix,
@@ -14611,9 +14781,9 @@ var TextInput = ({
14611
14781
  }
14612
14782
  });
14613
14783
  const { before, after } = splitAtCursor({ value, cursor });
14614
- return /* @__PURE__ */ jsxDEV8(Box6, {
14784
+ return /* @__PURE__ */ jsxDEV9(Box6, {
14615
14785
  children: [
14616
- /* @__PURE__ */ jsxDEV8(Text8, {
14786
+ /* @__PURE__ */ jsxDEV9(Text9, {
14617
14787
  bold: true,
14618
14788
  color: "green",
14619
14789
  children: [
@@ -14621,42 +14791,42 @@ var TextInput = ({
14621
14791
  " "
14622
14792
  ]
14623
14793
  }, undefined, true, undefined, this),
14624
- /* @__PURE__ */ jsxDEV8(Text8, {
14794
+ /* @__PURE__ */ jsxDEV9(Text9, {
14625
14795
  bold: true,
14626
14796
  children: [
14627
14797
  label,
14628
14798
  ": "
14629
14799
  ]
14630
14800
  }, undefined, true, undefined, this),
14631
- value ? /* @__PURE__ */ jsxDEV8(Fragment, {
14801
+ value ? /* @__PURE__ */ jsxDEV9(Fragment, {
14632
14802
  children: [
14633
- /* @__PURE__ */ jsxDEV8(Text8, {
14803
+ /* @__PURE__ */ jsxDEV9(Text9, {
14634
14804
  children: before
14635
14805
  }, undefined, false, undefined, this),
14636
- /* @__PURE__ */ jsxDEV8(Text8, {
14806
+ /* @__PURE__ */ jsxDEV9(Text9, {
14637
14807
  inverse: true,
14638
14808
  children: "▌"
14639
14809
  }, undefined, false, undefined, this),
14640
- /* @__PURE__ */ jsxDEV8(Text8, {
14810
+ /* @__PURE__ */ jsxDEV9(Text9, {
14641
14811
  children: after
14642
14812
  }, undefined, false, undefined, this)
14643
14813
  ]
14644
- }, undefined, true, undefined, this) : placeholder ? /* @__PURE__ */ jsxDEV8(Fragment, {
14814
+ }, undefined, true, undefined, this) : placeholder ? /* @__PURE__ */ jsxDEV9(Fragment, {
14645
14815
  children: [
14646
- /* @__PURE__ */ jsxDEV8(Text8, {
14816
+ /* @__PURE__ */ jsxDEV9(Text9, {
14647
14817
  inverse: true,
14648
14818
  children: "▌"
14649
14819
  }, undefined, false, undefined, this),
14650
- /* @__PURE__ */ jsxDEV8(Text8, {
14820
+ /* @__PURE__ */ jsxDEV9(Text9, {
14651
14821
  dimColor: true,
14652
14822
  children: placeholder
14653
14823
  }, undefined, false, undefined, this)
14654
14824
  ]
14655
- }, undefined, true, undefined, this) : /* @__PURE__ */ jsxDEV8(Text8, {
14825
+ }, undefined, true, undefined, this) : /* @__PURE__ */ jsxDEV9(Text9, {
14656
14826
  inverse: true,
14657
14827
  children: "▌"
14658
14828
  }, undefined, false, undefined, this),
14659
- historyIndex !== -1 ? /* @__PURE__ */ jsxDEV8(Text8, {
14829
+ historyIndex !== -1 ? /* @__PURE__ */ jsxDEV9(Text9, {
14660
14830
  dimColor: true,
14661
14831
  children: " (history)"
14662
14832
  }, undefined, false, undefined, this) : null
@@ -14860,7 +15030,7 @@ function dispatchSlashCommand(raw, ctx) {
14860
15030
  }
14861
15031
 
14862
15032
  // src/RunTui.tsx
14863
- import { jsxDEV as jsxDEV9 } from "react/jsx-dev-runtime";
15033
+ import { jsxDEV as jsxDEV10 } from "react/jsx-dev-runtime";
14864
15034
  var RunScreen = ({
14865
15035
  handleQuery,
14866
15036
  streamHandleQuery,
@@ -14898,7 +15068,7 @@ var RunScreen = ({
14898
15068
  onExit?.();
14899
15069
  exit();
14900
15070
  }, [onExit, exit]);
14901
- const executeQuery = useCallback4(async (message) => {
15071
+ const executeQuery = useCallback4(async (message, envEvent) => {
14902
15072
  isProcessingRef.current += 1;
14903
15073
  stream.start();
14904
15074
  let assistantBlocks = 0;
@@ -14911,6 +15081,18 @@ var RunScreen = ({
14911
15081
  { role: "assistant", content, id, continuation }
14912
15082
  ]);
14913
15083
  };
15084
+ let pendingUsage;
15085
+ const attachUsageToTurn = (usage) => {
15086
+ pendingUsage = usage;
15087
+ };
15088
+ const flushUsageFooter = () => {
15089
+ const usage = pendingUsage;
15090
+ pendingUsage = undefined;
15091
+ if (!usage)
15092
+ return;
15093
+ const id = `h${++historyIdRef.current}`;
15094
+ setHistory((h) => [...h, { role: "usage", content: "", id, usage }]);
15095
+ };
14914
15096
  const commitTool = (info) => {
14915
15097
  const id = `h${++historyIdRef.current}`;
14916
15098
  setHistory((h) => [
@@ -14987,17 +15169,18 @@ var RunScreen = ({
14987
15169
  const stable = stream.drainStable();
14988
15170
  if (stable)
14989
15171
  pushAssistant(stable);
14990
- }, showToolCalls ? handleToolEvent : undefined);
15172
+ }, showToolCalls ? handleToolEvent : undefined, attachUsageToTurn, envEvent);
14991
15173
  const tail = stream.finish(responseText);
14992
15174
  if (tail.trim())
14993
15175
  pushAssistant(tail);
14994
15176
  } else {
14995
- responseText = await handleQuery(message);
15177
+ responseText = await handleQuery(message, envEvent);
14996
15178
  stream.finish();
14997
15179
  const rendered = markdownEnabled2 ? renderMarkdown(responseText, { width }) : responseText;
14998
15180
  pushAssistant(truncateForTerminal(rendered, maxLines));
14999
15181
  }
15000
15182
  settleLiveTools("completed");
15183
+ flushUsageFooter();
15001
15184
  assistantTurnCountRef.current += 1;
15002
15185
  } catch (err) {
15003
15186
  const msg = err instanceof Error ? err.message : String(err);
@@ -15029,14 +15212,14 @@ var RunScreen = ({
15029
15212
  useEffect5(() => {
15030
15213
  if (!onRegisterHandlers)
15031
15214
  return;
15032
- const handleNotification = async (message, _envEvent) => {
15215
+ const handleNotification = async (message, envEvent) => {
15033
15216
  const notifId = `h${++historyIdRef.current}`;
15034
15217
  setHistory((h) => [
15035
15218
  ...h,
15036
15219
  { role: "tool", content: `\uD83D\uDCE2 ${message}`, id: notifId }
15037
15220
  ]);
15038
15221
  if (executeQueryRef.current) {
15039
- await executeQueryRef.current(message);
15222
+ await executeQueryRef.current(message, envEvent);
15040
15223
  }
15041
15224
  };
15042
15225
  const isIdle = () => {
@@ -15130,10 +15313,10 @@ var RunScreen = ({
15130
15313
  const handleCancel = useCallback4(() => {
15131
15314
  doExit();
15132
15315
  }, [doExit]);
15133
- return /* @__PURE__ */ jsxDEV9(Box7, {
15316
+ return /* @__PURE__ */ jsxDEV10(Box7, {
15134
15317
  flexDirection: "column",
15135
15318
  children: [
15136
- /* @__PURE__ */ jsxDEV9(App, {
15319
+ /* @__PURE__ */ jsxDEV10(App, {
15137
15320
  history,
15138
15321
  status,
15139
15322
  model,
@@ -15141,9 +15324,9 @@ var RunScreen = ({
15141
15324
  streamingText: stream.streamingText,
15142
15325
  liveTools
15143
15326
  }, undefined, false, undefined, this),
15144
- status === "idle" ? /* @__PURE__ */ jsxDEV9(Box7, {
15327
+ status === "idle" ? /* @__PURE__ */ jsxDEV10(Box7, {
15145
15328
  marginTop: 1,
15146
- children: /* @__PURE__ */ jsxDEV9(TextInput, {
15329
+ children: /* @__PURE__ */ jsxDEV10(TextInput, {
15147
15330
  label: "You",
15148
15331
  prefix: "\uD83D\uDC64",
15149
15332
  placeholder: 'Type a message, "/help", or "exit"',
@@ -15152,9 +15335,9 @@ var RunScreen = ({
15152
15335
  history: userHistory
15153
15336
  }, undefined, false, undefined, this)
15154
15337
  }, undefined, false, undefined, this) : null,
15155
- errorMessage ? /* @__PURE__ */ jsxDEV9(Box7, {
15338
+ errorMessage ? /* @__PURE__ */ jsxDEV10(Box7, {
15156
15339
  marginTop: 1,
15157
- children: /* @__PURE__ */ jsxDEV9(Text9, {
15340
+ children: /* @__PURE__ */ jsxDEV10(Text10, {
15158
15341
  color: "red",
15159
15342
  children: [
15160
15343
  "⚠ ",
@@ -15183,36 +15366,36 @@ async function shutdownRunSteps(deps) {
15183
15366
  envService,
15184
15367
  shouldDisposeEnvService,
15185
15368
  output,
15186
- logger: logger6
15369
+ logger: logger7
15187
15370
  } = deps;
15188
15371
  try {
15189
15372
  stopEventHandler();
15190
15373
  } catch (err) {
15191
- logger6?.warn?.(`[Shutdown] EventHandler.stop failed: ${err}`);
15374
+ logger7?.warn?.(`[Shutdown] EventHandler.stop failed: ${err}`);
15192
15375
  }
15193
15376
  try {
15194
15377
  stopTaskEventHandler();
15195
15378
  } catch (err) {
15196
- logger6?.warn?.(`[Shutdown] TaskEventHandler.stop failed: ${err}`);
15379
+ logger7?.warn?.(`[Shutdown] TaskEventHandler.stop failed: ${err}`);
15197
15380
  }
15198
15381
  if (lspManagerDispose) {
15199
15382
  try {
15200
15383
  await lspManagerDispose();
15201
15384
  } catch (err) {
15202
- logger6?.warn?.(`[Shutdown] LSP Manager dispose failed: ${err}`);
15385
+ logger7?.warn?.(`[Shutdown] LSP Manager dispose failed: ${err}`);
15203
15386
  }
15204
15387
  }
15205
15388
  if (pluginAdapterDispose) {
15206
15389
  try {
15207
15390
  await pluginAdapterDispose();
15208
15391
  } catch (err) {
15209
- logger6?.warn?.(`[Shutdown] PluginAdapter dispose failed: ${err}`);
15392
+ logger7?.warn?.(`[Shutdown] PluginAdapter dispose failed: ${err}`);
15210
15393
  }
15211
15394
  }
15212
15395
  try {
15213
15396
  queryExecutor.dispose();
15214
15397
  } catch (err) {
15215
- logger6?.warn?.(`[Shutdown] QueryExecutor.dispose failed: ${err}`);
15398
+ logger7?.warn?.(`[Shutdown] QueryExecutor.dispose failed: ${err}`);
15216
15399
  }
15217
15400
  try {
15218
15401
  const eventSourceComponent = env.getComponent("event-source");
@@ -15221,7 +15404,7 @@ async function shutdownRunSteps(deps) {
15221
15404
  if (sources && sources.length > 0) {
15222
15405
  for (const source of sources) {
15223
15406
  const status = eventSourceComponent.getStatus?.(source.id);
15224
- logger6?.debug?.(`[Shutdown] 检查事件源: ${source.id}, 状态: ${status}`);
15407
+ logger7?.debug?.(`[Shutdown] 检查事件源: ${source.id}, 状态: ${status}`);
15225
15408
  if (status !== "stopped" && status !== "created") {
15226
15409
  try {
15227
15410
  await eventSourceComponent.stopSource(source.id);
@@ -15234,20 +15417,20 @@ async function shutdownRunSteps(deps) {
15234
15417
  }
15235
15418
  }
15236
15419
  } catch (err) {
15237
- logger6?.warn?.(`[Shutdown] EventSource cleanup failed: ${err}`);
15420
+ logger7?.warn?.(`[Shutdown] EventSource cleanup failed: ${err}`);
15238
15421
  }
15239
15422
  if (shouldDisposeEnvService) {
15240
15423
  try {
15241
15424
  await envService.dispose();
15242
15425
  } catch (err) {
15243
- logger6?.warn?.(`[Shutdown] envService.dispose failed: ${err}`);
15426
+ logger7?.warn?.(`[Shutdown] envService.dispose failed: ${err}`);
15244
15427
  }
15245
15428
  }
15246
15429
  try {
15247
15430
  await TempFileManager3.getInstance().cleanupAll();
15248
- logger6?.debug?.("[Shutdown] TempFileManager cleaned up");
15431
+ logger7?.debug?.("[Shutdown] TempFileManager cleaned up");
15249
15432
  } catch (err) {
15250
- logger6?.warn?.(`[Shutdown] TempFileManager cleanup failed: ${err}`);
15433
+ logger7?.warn?.(`[Shutdown] TempFileManager cleanup failed: ${err}`);
15251
15434
  }
15252
15435
  }
15253
15436
 
@@ -15317,11 +15500,12 @@ async function loadHandleQuery(options, hooks) {
15317
15500
  }
15318
15501
  const llmComponent = envService.getLLM();
15319
15502
  queryExecutor.initStreamOutput(llmComponent ?? null);
15320
- const handleQuery = async (message) => {
15503
+ const handleQuery = async (message, envEvent) => {
15321
15504
  const mergedContext = {
15322
15505
  ...agentContext,
15323
15506
  model: options.model,
15324
15507
  eventSourceSystemPrompt,
15508
+ ...envEvent ? { envEvent } : {},
15325
15509
  metadata: {
15326
15510
  ...agentContext?.metadata ?? {},
15327
15511
  defaultAgentName: options.defaultAgentName
@@ -15366,7 +15550,7 @@ async function loadHandleQuery(options, hooks) {
15366
15550
  };
15367
15551
  }
15368
15552
  function createStreamHandleQuery(queryExecutor, sessionId, options, agentContext, eventSourceSystemPrompt) {
15369
- return async (message, onChunk, onTool) => {
15553
+ return async (message, onChunk, onTool, onUsage, envEvent) => {
15370
15554
  queryExecutor.setChunkCallback((event) => {
15371
15555
  switch (event.type) {
15372
15556
  case "llm.text":
@@ -15399,6 +15583,11 @@ function createStreamHandleQuery(queryExecutor, sessionId, options, agentContext
15399
15583
  error: event.toolError
15400
15584
  });
15401
15585
  break;
15586
+ case "llm.completed":
15587
+ if (event.usage) {
15588
+ onUsage?.(event.usage);
15589
+ }
15590
+ break;
15402
15591
  default:
15403
15592
  break;
15404
15593
  }
@@ -15407,6 +15596,7 @@ function createStreamHandleQuery(queryExecutor, sessionId, options, agentContext
15407
15596
  ...agentContext,
15408
15597
  model: options.model,
15409
15598
  eventSourceSystemPrompt,
15599
+ ...envEvent ? { envEvent } : {},
15410
15600
  metadata: {
15411
15601
  ...agentContext?.metadata ?? {},
15412
15602
  defaultAgentName: options.defaultAgentName
@@ -15581,7 +15771,7 @@ import { render as render2 } from "ink";
15581
15771
  import { useEffect as useEffect6, useRef as useRef4, useState as useState7 } from "react";
15582
15772
  import { Box as Box8, useApp as useApp3 } from "ink";
15583
15773
  init_markdown();
15584
- import { jsxDEV as jsxDEV10 } from "react/jsx-dev-runtime";
15774
+ import { jsxDEV as jsxDEV11 } from "react/jsx-dev-runtime";
15585
15775
  var ChatScreen = ({
15586
15776
  handleQuery,
15587
15777
  streamHandleQuery,
@@ -15637,9 +15827,9 @@ var ChatScreen = ({
15637
15827
  cancelled = true;
15638
15828
  };
15639
15829
  }, [handleQuery, streamHandleQuery, message, maxLines, markdownEnabled2, exit, width, stream]);
15640
- return /* @__PURE__ */ jsxDEV10(Box8, {
15830
+ return /* @__PURE__ */ jsxDEV11(Box8, {
15641
15831
  flexDirection: "column",
15642
- children: /* @__PURE__ */ jsxDEV10(App, {
15832
+ children: /* @__PURE__ */ jsxDEV11(App, {
15643
15833
  history,
15644
15834
  status: stream.status,
15645
15835
  model,