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

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