@pensar/apex 2.4.0 → 2.5.0-canary.2bbb0f6f

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.
Files changed (68) hide show
  1. package/build/agent-20hb6mg7.js +20 -0
  2. package/build/{agent-5ejt5vpz.js → agent-71sn1cjn.js} +9 -8
  3. package/build/{agent-nhs1w5w0.js → agent-xnrz4bhp.js} +11 -10
  4. package/build/{apps-17wvz1d7.js → apps-ghf7kp1s.js} +72 -31
  5. package/build/{auth-z3h9859n.js → auth-p1nrv7re.js} +25 -20
  6. package/build/authentication-m1vj2f0s.js +20 -0
  7. package/build/blackboxAgent-hjfzgk93.js +20 -0
  8. package/build/blackboxPentest-9snp9gvp.js +70 -0
  9. package/build/{cli-0n4r54hq.js → cli-08fcc6d8.js} +1 -1
  10. package/build/{cli-gq40s5b3.js → cli-14j00zfh.js} +5 -3
  11. package/build/{cli-vnynf0js.js → cli-3bmqdcfh.js} +10 -4
  12. package/build/{cli-mavved45.js → cli-3ew5fs3f.js} +46 -25
  13. package/build/{cli-xshasbwe.js → cli-4mgj070h.js} +30 -4
  14. package/build/{cli-repbyhkk.js → cli-4vwnsyep.js} +1141 -525
  15. package/build/cli-53rm0rrz.js +15 -0
  16. package/build/cli-68260ckj.js +12136 -0
  17. package/build/cli-7q3ae3ft.js +6 -0
  18. package/build/{cli-5k33webc.js → cli-7sqb16dd.js} +45 -43
  19. package/build/{cli-9rhzhgx2.js → cli-8pk8znhw.js} +1 -1
  20. package/build/cli-9pbk8m32.js +32 -0
  21. package/build/{cli-72cvcqhg.js → cli-9ya3yktn.js} +1 -1
  22. package/build/{cli-vq52kp7j.js → cli-a8n64cef.js} +23 -5
  23. package/build/{cli-qqt4gj1w.js → cli-a9vq54sx.js} +5 -3
  24. package/build/{cli-swpbx60z.js → cli-avmhkjtv.js} +1 -1481
  25. package/build/{cli-tkc598ey.js → cli-f47dgye3.js} +411 -280
  26. package/build/{cli-k8kc47pq.js → cli-fw7sp0fp.js} +3 -3
  27. package/build/{cli-32z0017y.js → cli-g3cb8pj5.js} +2 -2
  28. package/build/{cli-tznv8pf1.js → cli-gbch9xmy.js} +1 -1
  29. package/build/cli-knd276ed.js +1485 -0
  30. package/build/{cli-2cbwdk78.js → cli-t7rhyqx5.js} +1 -1
  31. package/build/{cli-b1adytsy.js → cli-yge44bmk.js} +28 -5
  32. package/build/cli.js +641 -95
  33. package/build/{config-f1mj4nhd.js → config-cqw98n5y.js} +18 -15
  34. package/build/{doctor-ekneewa3.js → doctor-vzh1wmfa.js} +8 -7
  35. package/build/{fastStrike-8zka43xs.js → fastStrike-f441e9nr.js} +13 -10
  36. package/build/{fixes-b89s5haw.js → fixes-xxz52cf2.js} +25 -20
  37. package/build/getMachineId-bsd-xj3bn2fv.js +36 -0
  38. package/build/getMachineId-darwin-07jzjcx6.js +36 -0
  39. package/build/getMachineId-linux-ahngynxd.js +29 -0
  40. package/build/getMachineId-unsupported-vy43b44z.js +19 -0
  41. package/build/getMachineId-win-8x66cynz.js +38 -0
  42. package/build/{index-ghwf5z3v.js → index-23jj0234.js} +3 -3
  43. package/build/{index-6d22pyxt.js → index-3gavwzay.js} +1756 -1213
  44. package/build/{index-4ad3sdwk.js → index-4v5jc2td.js} +4 -4
  45. package/build/{index-mtq7kmxz.js → index-5wh7gq37.js} +4 -2
  46. package/build/{index-sf16jm0m.js → index-e7kq5rrd.js} +9 -7
  47. package/build/{index-rbzhk712.js → index-fxx38nc9.js} +11 -10
  48. package/build/{index-hqjdg6fg.js → index-j3dc7dks.js} +17 -9
  49. package/build/{index-y5w6n3tb.js → index-yc1dnj3n.js} +2 -2
  50. package/build/{issues-xx5bs4md.js → issues-zdag9ayw.js} +82 -26
  51. package/build/{logs-qtxe51w2.js → logs-f492cjgt.js} +25 -20
  52. package/build/{offesecAgent-b9e2xm6v.js → offesecAgent-pr0cmrqb.js} +10 -9
  53. package/build/pentest-1g3fzah5.js +30 -0
  54. package/build/{pentests-6ks3szc4.js → pentests-vstpzh9j.js} +26 -21
  55. package/build/{targetedPentest-s1ptvq8e.js → targetedPentest-wj3ae355.js} +11 -10
  56. package/build/{targets-sxygyj87.js → targets-806zxf1n.js} +27 -22
  57. package/build/threatModel-dc4jbhkf.js +29 -0
  58. package/build/{uninstall-tvsh81v8.js → uninstall-58ak2e2v.js} +6 -3
  59. package/build/{upload-7wdyyqmr.js → upload-kkktzh83.js} +8 -7
  60. package/build/{utils-7fwdkqk6.js → utils-1sawtwbj.js} +7 -6
  61. package/package.json +8 -2
  62. package/build/agent-mvpgvgks.js +0 -19
  63. package/build/authentication-3s97cpk4.js +0 -19
  64. package/build/blackboxAgent-gw0a1ca4.js +0 -19
  65. package/build/blackboxPentest-44333zss.js +0 -37
  66. package/build/main-mmjp338j.js +0 -324
  67. package/build/pentest-xtc1sjdw.js +0 -29
  68. package/build/threatModel-xsm6fwk5.js +0 -27
@@ -7,23 +7,36 @@ import {
7
7
  buildThreatModelPrompt,
8
8
  createSkillsRegistry,
9
9
  runOffensiveSecurityAgent
10
- } from "./cli-vq52kp7j.js";
10
+ } from "./cli-a8n64cef.js";
11
+ import {
12
+ EMPTY_SESSION_TOKEN_USAGE,
13
+ accumulateSessionTokens,
14
+ contextPercentage
15
+ } from "./cli-9pbk8m32.js";
11
16
  import {
12
17
  convertModelMessagesToUI,
13
18
  loadSubagents,
14
19
  readExecutionMetrics,
15
20
  writeExecutionMetrics
16
- } from "./cli-5k33webc.js";
17
- import"./cli-gq40s5b3.js";
21
+ } from "./cli-7sqb16dd.js";
22
+ import"./cli-fw7sp0fp.js";
23
+ import"./cli-14j00zfh.js";
24
+ import"./cli-3ew5fs3f.js";
18
25
  import {
19
26
  REPORT_FILENAME_MD
20
- } from "./cli-b1adytsy.js";
21
- import"./cli-mavved45.js";
22
- import"./cli-xshasbwe.js";
23
- import"./cli-32z0017y.js";
24
- import"./cli-k8kc47pq.js";
27
+ } from "./cli-yge44bmk.js";
28
+ import {
29
+ createWeaveTracer,
30
+ resolveConfig
31
+ } from "./cli-t7rhyqx5.js";
32
+ import {
33
+ AVAILABLE_PROVIDERS,
34
+ init_types
35
+ } from "./cli-ee3axgts.js";
36
+ import"./cli-4mgj070h.js";
37
+ import"./cli-g3cb8pj5.js";
25
38
  import"./cli-9fsre5pt.js";
26
- import"./cli-qqt4gj1w.js";
39
+ import"./cli-a9vq54sx.js";
27
40
  import {
28
41
  ALL_TOOL_NAMES,
29
42
  ASK_USER_QUESTIONS_TOOL_NAME,
@@ -39,10 +52,19 @@ import {
39
52
  hasPlan,
40
53
  planFilePath,
41
54
  readPlan
42
- } from "./cli-repbyhkk.js";
55
+ } from "./cli-4vwnsyep.js";
43
56
  import {
44
57
  detectOSAndEnhancePrompt
45
- } from "./cli-tznv8pf1.js";
58
+ } from "./cli-gbch9xmy.js";
59
+ import {
60
+ require_index_node_http,
61
+ require_index_shim,
62
+ require_src as require_src2,
63
+ require_src1 as require_src3,
64
+ require_src2 as require_src4,
65
+ require_src3 as require_src5,
66
+ require_src4 as require_src6
67
+ } from "./cli-68260ckj.js";
46
68
  import {
47
69
  formatParseError,
48
70
  parseHeaderLine
@@ -55,20 +77,18 @@ import {
55
77
  getAutoPopulatedPorts,
56
78
  parseTargetUrl
57
79
  } from "./cli-c8131c4q.js";
58
- import {
59
- createWeaveTracer,
60
- resolveConfig
61
- } from "./cli-2cbwdk78.js";
62
- import {
63
- AVAILABLE_PROVIDERS,
64
- init_types
65
- } from "./cli-ee3axgts.js";
66
80
  import {
67
81
  AVAILABLE_MODELS,
68
82
  DEFAULT_OPENAI_REASONING_EFFORT,
83
+ addRecentModelId,
69
84
  buildAuthConfig,
85
+ endAllActiveRootSpans,
86
+ getContextWindow,
70
87
  getOpenAIReasoningEfforts,
88
+ getRecentModels,
89
+ getResumeMessages,
71
90
  hasToolCall,
91
+ init_active_root_spans,
72
92
  init_ai,
73
93
  init_dist,
74
94
  init_logger,
@@ -85,7 +105,7 @@ import {
85
105
  stepCountIs,
86
106
  write,
87
107
  writeErrorLog
88
- } from "./cli-tkc598ey.js";
108
+ } from "./cli-f47dgye3.js";
89
109
  import {
90
110
  disconnect,
91
111
  ensureValidToken,
@@ -100,21 +120,23 @@ import {
100
120
  selectWorkspace,
101
121
  startDeviceFlow,
102
122
  validateGateway
103
- } from "./cli-0n4r54hq.js";
104
- import"./cli-swpbx60z.js";
123
+ } from "./cli-08fcc6d8.js";
124
+ import"./cli-avmhkjtv.js";
105
125
  import"./cli-j54qa6tm.js";
106
126
  import {
107
127
  config,
108
- init_config as init_config2
109
- } from "./cli-72cvcqhg.js";
110
- import {
111
- init_config,
112
- update
113
- } from "./cli-9rhzhgx2.js";
128
+ init_config
129
+ } from "./cli-9ya3yktn.js";
130
+ import"./cli-8pk8znhw.js";
114
131
  import {
115
132
  checkForUpdate,
116
- init_installation
117
- } from "./cli-vnynf0js.js";
133
+ init_installation,
134
+ init_package,
135
+ version
136
+ } from "./cli-3bmqdcfh.js";
137
+ import {
138
+ require_src
139
+ } from "./cli-knd276ed.js";
118
140
  import {
119
141
  __commonJS,
120
142
  __esm,
@@ -752,11 +774,11 @@ var require_core = __commonJS(function(exports, module) {
752
774
  var warn = (message, ...args) => {
753
775
  console.log(`WARN: ${message}`, ...args);
754
776
  };
755
- var deprecated = (version2, message) => {
756
- if (seenDeprecations[`${version2}/${message}`])
777
+ var deprecated = (version3, message) => {
778
+ if (seenDeprecations[`${version3}/${message}`])
757
779
  return;
758
- console.log(`Deprecated as of ${version2}. ${message}`);
759
- seenDeprecations[`${version2}/${message}`] = true;
780
+ console.log(`Deprecated as of ${version3}. ${message}`);
781
+ seenDeprecations[`${version3}/${message}`] = true;
760
782
  };
761
783
  var MultiClassError = new Error;
762
784
  function remapScopeNames(mode, regexes, { key }) {
@@ -1006,7 +1028,7 @@ var require_core = __commonJS(function(exports, module) {
1006
1028
  }
1007
1029
  return mode;
1008
1030
  }
1009
- var version = "11.11.1";
1031
+ var version2 = "11.11.1";
1010
1032
 
1011
1033
  class HTMLInjectionError extends Error {
1012
1034
  constructor(reason, html) {
@@ -1070,13 +1092,13 @@ https://github.com/highlightjs/highlight.js/issues/2277`);
1070
1092
  if (ignoreIllegals === undefined) {
1071
1093
  ignoreIllegals = true;
1072
1094
  }
1073
- const context = {
1095
+ const context2 = {
1074
1096
  code,
1075
1097
  language: languageName
1076
1098
  };
1077
- fire("before:highlight", context);
1078
- const result = context.result ? context.result : _highlight(context.language, context.code, ignoreIllegals);
1079
- result.code = context.code;
1099
+ fire("before:highlight", context2);
1100
+ const result = context2.result ? context2.result : _highlight(context2.language, context2.code, ignoreIllegals);
1101
+ result.code = context2.code;
1080
1102
  fire("after:highlight", result);
1081
1103
  return result;
1082
1104
  }
@@ -1628,7 +1650,7 @@ https://github.com/highlightjs/highlight.js/issues/2277`);
1628
1650
  hljs.safeMode = function() {
1629
1651
  SAFE_MODE = true;
1630
1652
  };
1631
- hljs.versionString = version;
1653
+ hljs.versionString = version2;
1632
1654
  hljs.regex = {
1633
1655
  concat,
1634
1656
  lookahead,
@@ -49370,13 +49392,186 @@ var require_lib = __commonJS(function(exports, module) {
49370
49392
  });
49371
49393
 
49372
49394
  // src/tui/index.tsx
49373
- init_config2();
49395
+ init_config();
49374
49396
  init_installation();
49375
49397
  init_logger();
49376
49398
  import { createCliRenderer, FrameBufferRenderable, RGBA as RGBA25 } from "@opentui/core";
49377
49399
  import { createRoot, extend } from "@opentui/react";
49378
49400
  import { useEffect as useEffect27, useState as useState40 } from "react";
49379
49401
 
49402
+ // src/core/observability/runtime.ts
49403
+ init_package();
49404
+ init_active_root_spans();
49405
+ var import_api = __toESM(require_src(), 1);
49406
+ var import_context_async_hooks = __toESM(require_src2(), 1);
49407
+ var import_exporter_trace_otlp_http = __toESM(require_src6(), 1);
49408
+ var import_otlp_exporter_base = __toESM(require_src3(), 1);
49409
+ var import_node_http = __toESM(require_index_node_http(), 1);
49410
+ var import_otlp_transformer = __toESM(require_src5(), 1);
49411
+ var import_resources = __toESM(require_src4(), 1);
49412
+ var import_sdk_trace_base = __toESM(require_index_shim(), 1);
49413
+ import { randomUUID } from "node:crypto";
49414
+ var DEFAULT_SHUTDOWN_TIMEOUT_MS = 5000;
49415
+ function withTimeout(promise, timeoutMs) {
49416
+ return new Promise((resolve) => {
49417
+ let settled = false;
49418
+ const settle = (result) => {
49419
+ if (settled)
49420
+ return;
49421
+ settled = true;
49422
+ clearTimeout(timer);
49423
+ resolve(result);
49424
+ };
49425
+ const timer = setTimeout(() => settle("timed-out"), timeoutMs);
49426
+ promise.then(() => settle("completed"), () => settle("completed"));
49427
+ });
49428
+ }
49429
+ var NOOP_RUNTIME = {
49430
+ async forceFlush() {},
49431
+ async shutdown() {
49432
+ return "completed";
49433
+ }
49434
+ };
49435
+ function isOtelSdkDisabled(env) {
49436
+ return env.OTEL_SDK_DISABLED === "true";
49437
+ }
49438
+ function resolveTracesEndpoint(env) {
49439
+ const specific = env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT?.trim();
49440
+ if (specific)
49441
+ return specific;
49442
+ const generic = env.OTEL_EXPORTER_OTLP_ENDPOINT?.trim();
49443
+ if (!generic)
49444
+ return null;
49445
+ return `${generic.endsWith("/") ? generic : `${generic}/`}v1/traces`;
49446
+ }
49447
+ function resolveTracesProtocol(env) {
49448
+ const raw = (env.OTEL_EXPORTER_OTLP_TRACES_PROTOCOL ?? env.OTEL_EXPORTER_OTLP_PROTOCOL ?? "").trim();
49449
+ return raw === "http/protobuf" ? "http/protobuf" : "http/json";
49450
+ }
49451
+ function parseResourceAttributes(env) {
49452
+ const raw = env.OTEL_RESOURCE_ATTRIBUTES;
49453
+ if (!raw)
49454
+ return {};
49455
+ const attrs = {};
49456
+ for (const pair of raw.split(",")) {
49457
+ const eq = pair.indexOf("=");
49458
+ if (eq <= 0)
49459
+ continue;
49460
+ const key = pair.slice(0, eq).trim();
49461
+ const value = pair.slice(eq + 1).trim();
49462
+ if (key && value)
49463
+ attrs[key] = value;
49464
+ }
49465
+ return attrs;
49466
+ }
49467
+ function buildResource(env) {
49468
+ const resourceAttributes = parseResourceAttributes(env);
49469
+ return import_resources.resourceFromAttributes({
49470
+ "service.version": version,
49471
+ "service.instance.id": randomUUID(),
49472
+ ...resourceAttributes,
49473
+ "service.name": env.OTEL_SERVICE_NAME?.trim() || resourceAttributes["service.name"] || "apex"
49474
+ });
49475
+ }
49476
+ function createTraceExporter(protocol) {
49477
+ if (protocol === "http/json") {
49478
+ return new import_exporter_trace_otlp_http.OTLPTraceExporter;
49479
+ }
49480
+ return new import_otlp_exporter_base.OTLPExporterBase(import_node_http.createOtlpHttpExportDelegate(import_node_http.convertLegacyHttpOptions({}, "TRACES", "v1/traces", {
49481
+ "Content-Type": "application/x-protobuf"
49482
+ }), import_otlp_transformer.ProtobufTraceSerializer, "otlp_http_span_exporter", import_otlp_transformer.TraceExporterMetricsHelper, undefined));
49483
+ }
49484
+ var activeRuntime = null;
49485
+ function startObservabilityRuntime(env = process.env, opts) {
49486
+ if (activeRuntime)
49487
+ return activeRuntime;
49488
+ if (isOtelSdkDisabled(env))
49489
+ return NOOP_RUNTIME;
49490
+ if (!resolveTracesEndpoint(env))
49491
+ return NOOP_RUNTIME;
49492
+ const shutdownTimeoutMs = opts?.shutdownTimeoutMs ?? DEFAULT_SHUTDOWN_TIMEOUT_MS;
49493
+ const exporter = createTraceExporter(resolveTracesProtocol(env));
49494
+ const provider = new import_sdk_trace_base.BasicTracerProvider({
49495
+ resource: buildResource(env),
49496
+ spanProcessors: opts?.spanProcessors ?? [new import_sdk_trace_base.BatchSpanProcessor(exporter)]
49497
+ });
49498
+ const contextManager = new import_context_async_hooks.AsyncLocalStorageContextManager;
49499
+ contextManager.enable();
49500
+ import_api.trace.setGlobalTracerProvider(provider);
49501
+ import_api.context.setGlobalContextManager(contextManager);
49502
+ let shutdownPromise = null;
49503
+ const runtime = {
49504
+ async forceFlush() {
49505
+ await provider.forceFlush();
49506
+ },
49507
+ shutdown() {
49508
+ if (!shutdownPromise) {
49509
+ const shutdownWork = (async () => {
49510
+ endAllActiveRootSpans();
49511
+ try {
49512
+ await provider.forceFlush();
49513
+ } finally {
49514
+ await provider.shutdown();
49515
+ }
49516
+ })();
49517
+ shutdownPromise = withTimeout(shutdownWork, shutdownTimeoutMs).then((result) => {
49518
+ contextManager.disable();
49519
+ import_api.trace.disable();
49520
+ import_api.context.disable();
49521
+ return result;
49522
+ }).finally(() => {
49523
+ activeRuntime = null;
49524
+ });
49525
+ }
49526
+ return shutdownPromise;
49527
+ }
49528
+ };
49529
+ activeRuntime = runtime;
49530
+ return runtime;
49531
+ }
49532
+ function installObservabilityExitHandlers(runtime, opts) {
49533
+ let exitPromise = null;
49534
+ let exitCode = 0;
49535
+ let fatalSeen = false;
49536
+ const reportError = (error, source) => {
49537
+ try {
49538
+ opts?.onError?.(error, source);
49539
+ } catch {}
49540
+ };
49541
+ const exitWith = (code, error, source) => {
49542
+ let pendingReport = null;
49543
+ if (error !== undefined && !fatalSeen) {
49544
+ fatalSeen = true;
49545
+ exitCode = 1;
49546
+ const fatalSource = source ?? "uncaughtException";
49547
+ pendingReport = () => reportError(error, fatalSource);
49548
+ } else if (!fatalSeen && exitCode === 0) {
49549
+ exitCode = code;
49550
+ }
49551
+ if (exitPromise) {
49552
+ pendingReport?.();
49553
+ return exitPromise;
49554
+ }
49555
+ try {
49556
+ opts?.cleanup?.(code);
49557
+ } catch (cleanupError) {
49558
+ if (!fatalSeen) {
49559
+ fatalSeen = true;
49560
+ exitCode = 1;
49561
+ pendingReport = () => reportError(cleanupError, "uncaughtException");
49562
+ }
49563
+ }
49564
+ pendingReport?.();
49565
+ exitPromise = runtime.shutdown().catch(() => {}).then(() => process.exit(exitCode));
49566
+ return exitPromise;
49567
+ };
49568
+ process.on("SIGINT", () => void exitWith(130));
49569
+ process.on("SIGTERM", () => void exitWith(143));
49570
+ process.on("uncaughtException", (error) => void exitWith(1, error, "uncaughtException"));
49571
+ process.on("unhandledRejection", (reason) => void exitWith(1, reason, "unhandledRejection"));
49572
+ return exitWith;
49573
+ }
49574
+
49380
49575
  // src/core/providers/index.ts
49381
49576
  init_types();
49382
49577
  init_utils();
@@ -52796,16 +52991,15 @@ function createClipboardManager(renderer) {
52796
52991
 
52797
52992
  // src/tui/components/chat/index.tsx
52798
52993
  init_session();
52799
- import { useCallback as useCallback12, useEffect as useEffect12, useState as useState19 } from "react";
52994
+ import { useCallback as useCallback14, useEffect as useEffect12, useState as useState19 } from "react";
52800
52995
 
52801
52996
  // src/tui/context/agent.tsx
52802
52997
  init_ai();
52803
- init_config();
52804
52998
  init_logger();
52805
52999
  init_utils();
52806
53000
  import {
52807
53001
  createContext as createContext3,
52808
- useCallback as useCallback2,
53002
+ useCallback as useCallback4,
52809
53003
  useContext as useContext3,
52810
53004
  useEffect as useEffect2,
52811
53005
  useMemo as useMemo3,
@@ -52814,9 +53008,10 @@ import {
52814
53008
  } from "react";
52815
53009
 
52816
53010
  // src/tui/context/config.tsx
52817
- init_config2();
53011
+ init_config();
52818
53012
  import {
52819
53013
  createContext as createContext2,
53014
+ useCallback as useCallback2,
52820
53015
  useContext as useContext2,
52821
53016
  useMemo as useMemo2,
52822
53017
  useState as useState2
@@ -52825,20 +53020,22 @@ import { jsxDEV as jsxDEV2 } from "@opentui/react/jsx-dev-runtime";
52825
53020
  var ctx = createContext2(null);
52826
53021
  function ConfigProvider({ children, config: config2 }) {
52827
53022
  const [appConfig, setAppConfig] = useState2(config2);
53023
+ const update = useCallback2(async (newConfig) => {
53024
+ await config.update(newConfig);
53025
+ setAppConfig((current) => ({
53026
+ ...current,
53027
+ ...newConfig
53028
+ }));
53029
+ }, []);
53030
+ const reload = useCallback2(async () => {
53031
+ const freshConfig = await config.get();
53032
+ setAppConfig(freshConfig);
53033
+ }, []);
52828
53034
  const value = useMemo2(() => ({
52829
53035
  data: appConfig,
52830
- update: async (newConfig) => {
52831
- await config.update(newConfig);
52832
- setAppConfig({
52833
- ...appConfig,
52834
- ...newConfig
52835
- });
52836
- },
52837
- reload: async () => {
52838
- const freshConfig = await config.get();
52839
- setAppConfig(freshConfig);
52840
- }
52841
- }), [appConfig]);
53036
+ update,
53037
+ reload
53038
+ }), [appConfig, reload, update]);
52842
53039
  return /* @__PURE__ */ jsxDEV2(ctx.Provider, {
52843
53040
  value,
52844
53041
  children
@@ -52852,15 +53049,133 @@ var useConfig = () => {
52852
53049
  return config2;
52853
53050
  };
52854
53051
 
53052
+ // src/tui/context/session-usage.ts
53053
+ import { useCallback as useCallback3, useSyncExternalStore } from "react";
53054
+ var EMPTY_STATE = {
53055
+ tokenUsage: EMPTY_SESSION_TOKEN_USAGE,
53056
+ contextUsage: null
53057
+ };
53058
+
53059
+ class SessionUsageStore {
53060
+ sessions = new Map;
53061
+ listeners = new Map;
53062
+ activeListeners = new Set;
53063
+ activeId = null;
53064
+ provisionalState = EMPTY_STATE;
53065
+ get activeSessionId() {
53066
+ return this.activeId;
53067
+ }
53068
+ beginNewSession() {
53069
+ const changed = this.activeId !== null || this.provisionalState !== EMPTY_STATE;
53070
+ this.activeId = null;
53071
+ this.provisionalState = EMPTY_STATE;
53072
+ if (changed)
53073
+ this.emitActive();
53074
+ }
53075
+ enterSession(sessionId, seed) {
53076
+ if (!this.sessions.has(sessionId)) {
53077
+ this.sessions.set(sessionId, {
53078
+ tokenUsage: seed?.tokenUsage ?? this.provisionalState.tokenUsage,
53079
+ contextUsage: seed?.contextUsage ?? null
53080
+ });
53081
+ this.provisionalState = EMPTY_STATE;
53082
+ }
53083
+ if (this.activeId !== sessionId) {
53084
+ this.activeId = sessionId;
53085
+ this.emit(sessionId);
53086
+ }
53087
+ }
53088
+ forgetSession(sessionId) {
53089
+ this.sessions.delete(sessionId);
53090
+ this.listeners.delete(sessionId);
53091
+ if (this.activeId === sessionId)
53092
+ this.activeId = null;
53093
+ }
53094
+ getSnapshot(sessionId) {
53095
+ if (sessionId === null)
53096
+ return EMPTY_STATE;
53097
+ return this.sessions.get(sessionId) ?? EMPTY_STATE;
53098
+ }
53099
+ subscribe(sessionId, listener) {
53100
+ let set = this.listeners.get(sessionId);
53101
+ if (!set) {
53102
+ set = new Set;
53103
+ this.listeners.set(sessionId, set);
53104
+ }
53105
+ set.add(listener);
53106
+ return () => {
53107
+ set.delete(listener);
53108
+ };
53109
+ }
53110
+ addSessionTokens(sessionId, step) {
53111
+ if (sessionId === null) {
53112
+ const next = accumulateSessionTokens(this.provisionalState.tokenUsage, step);
53113
+ if (next !== this.provisionalState.tokenUsage) {
53114
+ this.provisionalState = { tokenUsage: next, contextUsage: null };
53115
+ if (this.activeId === null)
53116
+ this.emitActive();
53117
+ }
53118
+ return;
53119
+ }
53120
+ this.update(sessionId, (state) => ({
53121
+ ...state,
53122
+ tokenUsage: accumulateSessionTokens(state.tokenUsage, step)
53123
+ }));
53124
+ }
53125
+ setRootContext(sessionId, sample) {
53126
+ if (sessionId === null)
53127
+ return;
53128
+ this.update(sessionId, (state) => ({
53129
+ ...state,
53130
+ contextUsage: sample
53131
+ }));
53132
+ }
53133
+ update(sessionId, updater) {
53134
+ const current = this.sessions.get(sessionId);
53135
+ if (!current)
53136
+ return;
53137
+ const next = updater(current);
53138
+ if (next === current || next.tokenUsage === current.tokenUsage && next.contextUsage === current.contextUsage) {
53139
+ return;
53140
+ }
53141
+ this.sessions.set(sessionId, next);
53142
+ this.emit(sessionId);
53143
+ }
53144
+ getActiveSnapshot() {
53145
+ return this.activeId === null ? this.provisionalState : this.getSnapshot(this.activeId);
53146
+ }
53147
+ subscribeActive(listener) {
53148
+ this.activeListeners.add(listener);
53149
+ return () => {
53150
+ this.activeListeners.delete(listener);
53151
+ };
53152
+ }
53153
+ emit(sessionId) {
53154
+ for (const listener of this.listeners.get(sessionId) ?? [])
53155
+ listener();
53156
+ if (sessionId === this.activeId)
53157
+ this.emitActive();
53158
+ }
53159
+ emitActive() {
53160
+ for (const listener of this.activeListeners)
53161
+ listener();
53162
+ }
53163
+ }
53164
+ function useActiveSessionUsage(store) {
53165
+ const subscribe = useCallback3((listener) => store.subscribeActive(listener), [store]);
53166
+ const getSnapshot = useCallback3(() => store.getActiveSnapshot(), [store]);
53167
+ return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
53168
+ }
53169
+
52855
53170
  // src/tui/context/agent.tsx
52856
53171
  import { jsxDEV as jsxDEV3 } from "@opentui/react/jsx-dev-runtime";
52857
53172
  var AgentContext = createContext3(null);
52858
53173
  function useAgent() {
52859
- const context = useContext3(AgentContext);
52860
- if (!context) {
53174
+ const context2 = useContext3(AgentContext);
53175
+ if (!context2) {
52861
53176
  throw new Error("useAgent must be used within AgentProvider");
52862
53177
  }
52863
- return context;
53178
+ return context2;
52864
53179
  }
52865
53180
  function AgentProvider({ children }) {
52866
53181
  const appConfig = useConfig();
@@ -52868,13 +53183,7 @@ function AgentProvider({ children }) {
52868
53183
  return getDefaultModelForConfig(appConfig.data) ?? AVAILABLE_MODELS[0];
52869
53184
  });
52870
53185
  const [isModelUserSelected, setIsModelUserSelected] = useState3(false);
52871
- const [tokenUsage, setTokenUsage] = useState3({
52872
- inputTokens: 0,
52873
- outputTokens: 0,
52874
- totalTokens: 0,
52875
- cachedTokens: 0,
52876
- cacheWriteTokens: 0
52877
- });
53186
+ const usageStore = useMemo3(() => new SessionUsageStore, []);
52878
53187
  const [hasExecuted, setHasExecuted] = useState3(false);
52879
53188
  const [thinking, setThinking] = useState3(false);
52880
53189
  const [reasoningEnabled, setReasoningEnabledInternal] = useState3(() => appConfig.data?.reasoningEnabled ?? false);
@@ -52885,12 +53194,17 @@ function AgentProvider({ children }) {
52885
53194
  reasoningEnabledRef.current = reasoningEnabled;
52886
53195
  const openAIReasoningEffortRef = useRef(openAIReasoningEffort);
52887
53196
  openAIReasoningEffortRef.current = openAIReasoningEffort;
52888
- const setModel = useCallback2((newModel, persist = true) => {
53197
+ const recentModelIdsRef = useRef(appConfig.data.recentModelIds ?? []);
53198
+ const updateConfig = appConfig.update;
53199
+ const setModel = useCallback4((newModel, persist = true) => {
52889
53200
  setModelInternal(newModel);
52890
53201
  if (persist) {
52891
53202
  setIsModelUserSelected(true);
53203
+ const nextRecentModelIds = addRecentModelId(recentModelIdsRef.current, newModel.id);
53204
+ recentModelIdsRef.current = nextRecentModelIds;
52892
53205
  const configUpdate = {
52893
- selectedModelId: newModel.id
53206
+ selectedModelId: newModel.id,
53207
+ recentModelIds: nextRecentModelIds
52894
53208
  };
52895
53209
  if (reasoningEnabledRef.current && !modelSupportsThinking(newModel.id)) {
52896
53210
  configUpdate.reasoningEnabled = false;
@@ -52901,23 +53215,26 @@ function AgentProvider({ children }) {
52901
53215
  configUpdate.openAIReasoningEffort = DEFAULT_OPENAI_REASONING_EFFORT;
52902
53216
  setOpenAIReasoningEffortInternal(DEFAULT_OPENAI_REASONING_EFFORT);
52903
53217
  }
52904
- update(configUpdate).catch((err) => {
53218
+ updateConfig(configUpdate).catch((err) => {
52905
53219
  writeErrorLog(err, "AGENT_CONTEXT");
52906
53220
  });
52907
53221
  }
52908
- }, []);
52909
- const setReasoningEnabled = useCallback2((enabled) => {
53222
+ }, [updateConfig]);
53223
+ const setReasoningEnabled = useCallback4((enabled) => {
52910
53224
  setReasoningEnabledInternal(enabled);
52911
- update({ reasoningEnabled: enabled }).catch((err) => {
53225
+ updateConfig({ reasoningEnabled: enabled }).catch((err) => {
52912
53226
  writeErrorLog(err, "AGENT_CONTEXT");
52913
53227
  });
52914
- }, []);
52915
- const setOpenAIReasoningEffort = useCallback2((effort) => {
53228
+ }, [updateConfig]);
53229
+ const setOpenAIReasoningEffort = useCallback4((effort) => {
52916
53230
  setOpenAIReasoningEffortInternal(effort);
52917
- update({ openAIReasoningEffort: effort }).catch((err) => {
53231
+ updateConfig({ openAIReasoningEffort: effort }).catch((err) => {
52918
53232
  writeErrorLog(err, "AGENT_CONTEXT");
52919
53233
  });
52920
- }, []);
53234
+ }, [updateConfig]);
53235
+ useEffect2(() => {
53236
+ recentModelIdsRef.current = appConfig.data.recentModelIds ?? [];
53237
+ }, [appConfig.data.recentModelIds]);
52921
53238
  useEffect2(() => {
52922
53239
  if (appConfig.data?.reasoningEnabled != null) {
52923
53240
  setReasoningEnabledInternal(appConfig.data.reasoningEnabled);
@@ -52948,40 +53265,13 @@ function AgentProvider({ children }) {
52948
53265
  setModelInternal(defaultModel);
52949
53266
  }
52950
53267
  }, [appConfig.data, isModelUserSelected]);
52951
- const addTokenUsage = useCallback2((input, output) => {
52952
- setHasExecuted(true);
52953
- setTokenUsage((prev) => ({
52954
- ...prev,
52955
- inputTokens: prev.inputTokens + input,
52956
- outputTokens: prev.outputTokens + output,
52957
- totalTokens: prev.totalTokens + input + output
52958
- }));
52959
- }, []);
52960
- const addCacheUsage = useCallback2((cacheRead, cacheWrite) => {
52961
- setTokenUsage((prev) => ({
52962
- ...prev,
52963
- cachedTokens: prev.cachedTokens + cacheRead,
52964
- cacheWriteTokens: prev.cacheWriteTokens + cacheWrite
52965
- }));
52966
- }, []);
52967
- const resetTokenUsage = useCallback2(() => {
52968
- setHasExecuted(false);
52969
- setTokenUsage({
52970
- inputTokens: 0,
52971
- outputTokens: 0,
52972
- totalTokens: 0,
52973
- cachedTokens: 0,
52974
- cacheWriteTokens: 0
52975
- });
52976
- }, []);
53268
+ const markExecuted = useCallback4(() => setHasExecuted(true), []);
52977
53269
  const contextValue = useMemo3(() => ({
52978
53270
  model,
52979
53271
  setModel,
52980
53272
  isModelUserSelected,
52981
- tokenUsage,
52982
- addTokenUsage,
52983
- addCacheUsage,
52984
- resetTokenUsage,
53273
+ usageStore,
53274
+ markExecuted,
52985
53275
  hasExecuted,
52986
53276
  thinking,
52987
53277
  setThinking,
@@ -52997,7 +53287,6 @@ function AgentProvider({ children }) {
52997
53287
  model,
52998
53288
  setModel,
52999
53289
  isModelUserSelected,
53000
- tokenUsage,
53001
53290
  hasExecuted,
53002
53291
  thinking,
53003
53292
  reasoningEnabled,
@@ -53005,9 +53294,8 @@ function AgentProvider({ children }) {
53005
53294
  openAIReasoningEffort,
53006
53295
  isExecuting,
53007
53296
  sessionCwd,
53008
- addTokenUsage,
53009
- addCacheUsage,
53010
- resetTokenUsage,
53297
+ usageStore,
53298
+ markExecuted,
53011
53299
  setOpenAIReasoningEffort
53012
53300
  ]);
53013
53301
  return /* @__PURE__ */ jsxDEV3(AgentContext.Provider, {
@@ -53019,14 +53307,14 @@ function AgentProvider({ children }) {
53019
53307
  // src/tui/components/chat/config-view.tsx
53020
53308
  init_utils();
53021
53309
  import { useKeyboard as useKeyboard2 } from "@opentui/react";
53022
- import { useCallback as useCallback4, useEffect as useEffect4, useState as useState5 } from "react";
53310
+ import { useCallback as useCallback6, useEffect as useEffect4, useState as useState5 } from "react";
53023
53311
 
53024
53312
  // src/tui/components/model-picker/ModelPicker.tsx
53025
53313
  init_ai();
53026
53314
  init_utils();
53027
53315
  import { useKeyboard } from "@opentui/react";
53028
53316
  import {
53029
- useCallback as useCallback3,
53317
+ useCallback as useCallback5,
53030
53318
  useEffect as useEffect3,
53031
53319
  useMemo as useMemo4,
53032
53320
  useRef as useRef2,
@@ -53094,18 +53382,105 @@ function findChildById(scrollBox, id) {
53094
53382
  return;
53095
53383
  }
53096
53384
 
53097
- // src/tui/components/model-picker/ModelPicker.tsx
53098
- import { jsxDEV as jsxDEV4 } from "@opentui/react/jsx-dev-runtime";
53099
- var providerNames = {
53100
- anthropic: "Claude",
53385
+ // src/tui/components/model-picker/model-navigation.ts
53386
+ function findSelectedModelIndex(navigationItems, selectedModelId) {
53387
+ return navigationItems.findIndex((item) => (item.type === "model" || item.type === "recent-model") && item.model?.id === selectedModelId);
53388
+ }
53389
+ function retainAvailableProviders(expandedProviders, availableProviders) {
53390
+ const retainedProviders = new Set([...expandedProviders].filter((provider) => availableProviders.has(provider)));
53391
+ return retainedProviders.size === expandedProviders.size ? expandedProviders : retainedProviders;
53392
+ }
53393
+ function getSelectedModelRevealKey({
53394
+ availableModels,
53395
+ selectedModel,
53396
+ lastRevealedSelectedModelKey
53397
+ }) {
53398
+ const selectedModelKey = `${selectedModel.provider}:${selectedModel.id}`;
53399
+ if (lastRevealedSelectedModelKey === selectedModelKey)
53400
+ return null;
53401
+ const selectedModelIsAvailable = availableModels.some((model) => model.id === selectedModel.id && model.provider === selectedModel.provider);
53402
+ return selectedModelIsAvailable ? selectedModelKey : null;
53403
+ }
53404
+
53405
+ // src/tui/components/model-picker/model-search.ts
53406
+ var PROVIDER_DISPLAY_NAMES = {
53407
+ anthropic: "Anthropic",
53101
53408
  openai: "OpenAI",
53102
- google: "Gemini",
53409
+ google: "Google",
53103
53410
  openrouter: "OpenRouter",
53104
53411
  concentrate: "Concentrate",
53105
- bedrock: "Bedrock",
53412
+ bedrock: "AWS Bedrock",
53413
+ "bedrock-mantle": "Bedrock Mantle",
53106
53414
  pensar: "Pensar",
53415
+ inception: "Inception",
53107
53416
  local: "Local LLM"
53108
53417
  };
53418
+ function getProviderDisplayName(provider) {
53419
+ return PROVIDER_DISPLAY_NAMES[provider];
53420
+ }
53421
+ function normalizeSearchText(value) {
53422
+ return value.toLowerCase().replace(/[^a-z0-9]+/g, " ").trim();
53423
+ }
53424
+ function filterModels(models, searchQuery) {
53425
+ const terms = normalizeSearchText(searchQuery).split(/\s+/).filter(Boolean);
53426
+ if (terms.length === 0)
53427
+ return models;
53428
+ return models.filter((model) => {
53429
+ const searchableText = normalizeSearchText(`${model.name} ${model.id} ${model.provider} ${getProviderDisplayName(model.provider)}`);
53430
+ return terms.every((term) => searchableText.includes(term));
53431
+ });
53432
+ }
53433
+
53434
+ // src/tui/components/model-picker/model-visibility.ts
53435
+ var HIDDEN_MODEL_IDS = {
53436
+ anthropic: new Set([
53437
+ "claude-3-haiku-20240307",
53438
+ "claude-opus-4-0",
53439
+ "claude-opus-4-20250514",
53440
+ "claude-opus-4-1",
53441
+ "claude-opus-4-1-20250805",
53442
+ "claude-sonnet-4-0",
53443
+ "claude-sonnet-4-20250514"
53444
+ ]),
53445
+ openai: new Set([
53446
+ "gpt-5.3-chat-latest",
53447
+ "gpt-5.2-chat-latest",
53448
+ "gpt-5.2-codex",
53449
+ "gpt-5.1-chat-latest",
53450
+ "gpt-5.1-codex-mini",
53451
+ "gpt-5.1-codex",
53452
+ "gpt-5.1-codex-max",
53453
+ "gpt-5-chat-latest",
53454
+ "gpt-5-codex",
53455
+ "gpt-4.1-nano",
53456
+ "gpt-4.1-nano-2025-04-14",
53457
+ "gpt-4o-2024-05-13",
53458
+ "gpt-3.5-turbo",
53459
+ "gpt-3.5-turbo-0125",
53460
+ "gpt-3.5-turbo-1106",
53461
+ "o4-mini",
53462
+ "o4-mini-2025-04-16",
53463
+ "o3-mini",
53464
+ "o3-mini-2025-01-31",
53465
+ "o1",
53466
+ "o1-2024-12-17"
53467
+ ]),
53468
+ google: new Set([
53469
+ "gemini-2.0-flash",
53470
+ "gemini-2.0-flash-001",
53471
+ "gemini-2.0-flash-lite",
53472
+ "gemini-2.0-flash-lite-001",
53473
+ "gemini-3-pro-preview",
53474
+ "gemini-3.1-flash-lite-preview",
53475
+ "gemini-robotics-er-1.5-preview"
53476
+ ])
53477
+ };
53478
+ function getVisiblePickerModels(models, selectedModel) {
53479
+ return models.filter((model) => !HIDDEN_MODEL_IDS[model.provider]?.has(model.id) || model.id === selectedModel?.id && model.provider === selectedModel.provider);
53480
+ }
53481
+
53482
+ // src/tui/components/model-picker/ModelPicker.tsx
53483
+ import { jsxDEV as jsxDEV4, Fragment } from "@opentui/react/jsx-dev-runtime";
53109
53484
  var providerOrder = [
53110
53485
  "pensar",
53111
53486
  "anthropic",
@@ -53114,11 +53489,10 @@ var providerOrder = [
53114
53489
  "openrouter",
53115
53490
  "concentrate",
53116
53491
  "bedrock",
53492
+ "bedrock-mantle",
53493
+ "inception",
53117
53494
  "local"
53118
53495
  ];
53119
- function setsAreEqual(a, b) {
53120
- return a.size === b.size && [...a].every((value) => b.has(value));
53121
- }
53122
53496
  function PickerRow({
53123
53497
  children,
53124
53498
  id,
@@ -53145,6 +53519,8 @@ function getNavItemId(item) {
53145
53519
  return `provider-${item.provider}`;
53146
53520
  if (item.type === "model")
53147
53521
  return `model-${item.model.id}`;
53522
+ if (item.type === "recent-model")
53523
+ return `recent-model-${item.model.id}`;
53148
53524
  if (item.type === "reasoning")
53149
53525
  return "reasoning-toggle";
53150
53526
  if (item.type === "openai-reasoning")
@@ -53169,8 +53545,10 @@ function ModelPicker({
53169
53545
  const scrollBoxRef = useRef2(null);
53170
53546
  const [availableModels, setAvailableModels] = useState4([]);
53171
53547
  const [searchQuery, setSearchQuery] = useState4("");
53172
- const [expandedProviders, setExpandedProviders] = useState4(new Set(["anthropic"]));
53548
+ const [expandedProviders, setExpandedProviders] = useState4(new Set([selectedModel.provider]));
53173
53549
  const [focusedIndex, setFocusedIndex] = useState4(0);
53550
+ const lastRevealedSelectedModelRef = useRef2(null);
53551
+ const lastFocusedSelectedModelIdRef = useRef2(null);
53174
53552
  const [localUrl, setLocalUrl] = useState4(config2?.localModelUrl ?? "");
53175
53553
  const [localModelName, setLocalModelName] = useState4(config2?.localModelName ?? "");
53176
53554
  const [editingLocalField, setEditingLocalField] = useState4(null);
@@ -53183,86 +53561,105 @@ function ModelPicker({
53183
53561
  setAvailableModels([]);
53184
53562
  return;
53185
53563
  }
53186
- setAvailableModels(getAvailableModels(config2));
53187
- }, [config2]);
53564
+ setAvailableModels(getVisiblePickerModels(getAvailableModels(config2), {
53565
+ id: selectedModel.id,
53566
+ provider: selectedModel.provider
53567
+ }));
53568
+ }, [config2, selectedModel.id, selectedModel.provider]);
53188
53569
  useEffect3(() => {
53189
53570
  const availableProviders = new Set(availableModels.map((model) => model.provider));
53190
- setExpandedProviders((prev) => {
53191
- if (availableProviders.size === 0) {
53192
- return prev.size === 0 ? prev : new Set;
53193
- }
53194
- const preservedProviders = new Set([...prev].filter((provider) => availableProviders.has(provider)));
53195
- if (preservedProviders.size > 0) {
53196
- return setsAreEqual(prev, preservedProviders) ? prev : preservedProviders;
53197
- }
53198
- const fallbackProvider = availableProviders.has(selectedModel.provider) ? selectedModel.provider : availableModels[0]?.provider;
53199
- if (!fallbackProvider) {
53200
- return prev.size === 0 ? prev : new Set;
53201
- }
53202
- return prev.size === 1 && prev.has(fallbackProvider) ? prev : new Set([fallbackProvider]);
53571
+ setExpandedProviders((prev) => retainAvailableProviders(prev, availableProviders));
53572
+ }, [availableModels]);
53573
+ useEffect3(() => {
53574
+ const selectedModelKey = getSelectedModelRevealKey({
53575
+ availableModels,
53576
+ selectedModel: {
53577
+ id: selectedModel.id,
53578
+ provider: selectedModel.provider
53579
+ },
53580
+ lastRevealedSelectedModelKey: lastRevealedSelectedModelRef.current
53203
53581
  });
53204
- }, [availableModels, selectedModel.provider]);
53582
+ if (!selectedModelKey)
53583
+ return;
53584
+ lastRevealedSelectedModelRef.current = selectedModelKey;
53585
+ setExpandedProviders((prev) => prev.has(selectedModel.provider) ? prev : new Set([...prev, selectedModel.provider]));
53586
+ }, [availableModels, selectedModel.id, selectedModel.provider]);
53587
+ const filteredModels = useMemo4(() => filterModels(availableModels, searchQuery), [availableModels, searchQuery]);
53588
+ const isSearching = searchQuery.trim().length > 0;
53589
+ const recentModels = useMemo4(() => getRecentModels(availableModels, config2?.recentModelIds ?? []), [availableModels, config2?.recentModelIds]);
53205
53590
  const groupedModels = useMemo4(() => {
53206
53591
  const groups = {};
53207
- const query = searchQuery.toLowerCase().trim();
53208
53592
  for (const m of availableModels) {
53209
- if (query && !m.name.toLowerCase().includes(query) && !m.id.toLowerCase().includes(query)) {
53210
- continue;
53211
- }
53212
53593
  if (!groups[m.provider]) {
53213
53594
  groups[m.provider] = [];
53214
53595
  }
53215
53596
  groups[m.provider].push(m);
53216
53597
  }
53217
53598
  return groups;
53218
- }, [availableModels, searchQuery]);
53599
+ }, [availableModels]);
53219
53600
  const navigationItems = useMemo4(() => {
53220
53601
  const items = [];
53221
- for (const provider of providerOrder) {
53222
- if (provider === "local") {
53223
- items.push({ type: "provider", provider: "local" });
53224
- if (expandedProviders.has("local")) {
53225
- items.push({ type: "local-input", field: "url" });
53226
- items.push({ type: "local-input", field: "model" });
53227
- const localModels = groupedModels.local;
53228
- if (localModels) {
53229
- for (const m of localModels) {
53230
- items.push({ type: "model", model: m });
53602
+ if (isSearching) {
53603
+ for (const model of filteredModels) {
53604
+ items.push({ type: "model", model });
53605
+ }
53606
+ } else {
53607
+ for (const model of recentModels) {
53608
+ items.push({ type: "recent-model", model });
53609
+ }
53610
+ for (const provider of providerOrder) {
53611
+ if (provider === "local") {
53612
+ items.push({ type: "provider", provider: "local" });
53613
+ if (expandedProviders.has("local")) {
53614
+ items.push({ type: "local-input", field: "url" });
53615
+ items.push({ type: "local-input", field: "model" });
53616
+ const localModels = groupedModels.local;
53617
+ if (localModels) {
53618
+ for (const m of localModels) {
53619
+ items.push({ type: "model", model: m });
53620
+ }
53231
53621
  }
53232
53622
  }
53623
+ continue;
53233
53624
  }
53234
- continue;
53235
- }
53236
- const models = groupedModels[provider];
53237
- if (!models || models.length === 0)
53238
- continue;
53239
- items.push({ type: "provider", provider });
53240
- if (expandedProviders.has(provider)) {
53241
- for (const m of models) {
53242
- items.push({ type: "model", model: m });
53625
+ const models = groupedModels[provider];
53626
+ if (!models || models.length === 0)
53627
+ continue;
53628
+ items.push({ type: "provider", provider });
53629
+ if (expandedProviders.has(provider)) {
53630
+ for (const m of models) {
53631
+ items.push({ type: "model", model: m });
53632
+ }
53243
53633
  }
53244
53634
  }
53245
53635
  }
53246
- if (onReasoningToggle && modelSupportsThinking(selectedModel.id)) {
53636
+ if (!isSearching && onReasoningToggle && modelSupportsThinking(selectedModel.id)) {
53247
53637
  items.push({ type: "reasoning" });
53248
53638
  }
53249
- if (onOpenAIReasoningEffortChange && modelSupportsOpenAIReasoning(selectedModel.id)) {
53639
+ if (!isSearching && onOpenAIReasoningEffortChange && modelSupportsOpenAIReasoning(selectedModel.id)) {
53250
53640
  items.push({ type: "openai-reasoning" });
53251
53641
  }
53252
53642
  return items;
53253
53643
  }, [
53254
53644
  groupedModels,
53645
+ filteredModels,
53646
+ recentModels,
53647
+ isSearching,
53255
53648
  expandedProviders,
53256
53649
  onReasoningToggle,
53257
53650
  onOpenAIReasoningEffortChange,
53258
53651
  selectedModel.id
53259
53652
  ]);
53260
53653
  useEffect3(() => {
53261
- const idx = navigationItems.findIndex((item) => item.type === "model" && item.model.id === selectedModel.id);
53262
- if (idx !== -1) {
53263
- setFocusedIndex(idx);
53654
+ if (isSearching || lastFocusedSelectedModelIdRef.current === selectedModel.id) {
53655
+ return;
53264
53656
  }
53265
- }, [selectedModel.id, navigationItems]);
53657
+ const index = findSelectedModelIndex(navigationItems, selectedModel.id);
53658
+ if (index === -1)
53659
+ return;
53660
+ lastFocusedSelectedModelIdRef.current = selectedModel.id;
53661
+ setFocusedIndex(index);
53662
+ }, [isSearching, navigationItems, selectedModel.id]);
53266
53663
  useEffect3(() => {
53267
53664
  setFocusedIndex((prev) => Math.min(prev, Math.max(0, navigationItems.length - 1)));
53268
53665
  }, [navigationItems.length]);
@@ -53272,16 +53669,16 @@ function ModelPicker({
53272
53669
  return;
53273
53670
  scrollToChild(scrollBoxRef.current, getNavItemId(item));
53274
53671
  }, [focusedIndex, navigationItems]);
53275
- const commitLocalConfig = useCallback3((url, modelName) => {
53672
+ const commitLocalConfig = useCallback5((url, modelName) => {
53276
53673
  if (!onConfigUpdate)
53277
53674
  return;
53278
- const update2 = {
53675
+ const update = {
53279
53676
  localModelUrl: url || null,
53280
53677
  localModelName: modelName || null
53281
53678
  };
53282
- onConfigUpdate(update2);
53679
+ onConfigUpdate(update);
53283
53680
  }, [onConfigUpdate]);
53284
- const finishEditing = useCallback3(() => {
53681
+ const finishEditing = useCallback5(() => {
53285
53682
  setEditingLocalField(null);
53286
53683
  commitLocalConfig(localUrl, localModelName);
53287
53684
  }, [localUrl, localModelName, commitLocalConfig]);
@@ -53290,7 +53687,7 @@ function ModelPicker({
53290
53687
  const openAIReasoningEfforts = getOpenAIReasoningEfforts(selectedModel.id);
53291
53688
  const isReasoningFocused = navigationItems[focusedIndex]?.type === "reasoning";
53292
53689
  const isOpenAIReasoningFocused = navigationItems[focusedIndex]?.type === "openai-reasoning";
53293
- const cycleOpenAIReasoningEffort = useCallback3(() => {
53690
+ const cycleOpenAIReasoningEffort = useCallback5(() => {
53294
53691
  if (!onOpenAIReasoningEffortChange || openAIReasoningEfforts.length === 0) {
53295
53692
  return;
53296
53693
  }
@@ -53302,7 +53699,7 @@ function ModelPicker({
53302
53699
  openAIReasoningEffort,
53303
53700
  openAIReasoningEfforts
53304
53701
  ]);
53305
- const handleKeyboard = useCallback3((key) => {
53702
+ const handleKeyboard = useCallback5((key) => {
53306
53703
  if (!focused)
53307
53704
  return false;
53308
53705
  if (editingLocalField) {
@@ -53312,24 +53709,18 @@ function ModelPicker({
53312
53709
  }
53313
53710
  return false;
53314
53711
  }
53315
- if (navigationItems.length === 0)
53316
- return false;
53317
53712
  if (key.name === "up" || key.name === "down") {
53318
- const newIndex = key.name === "up" ? Math.max(0, focusedIndex - 1) : Math.min(navigationItems.length - 1, focusedIndex + 1);
53713
+ if (navigationItems.length === 0)
53714
+ return false;
53715
+ const direction = key.name === "up" ? -1 : 1;
53716
+ const newIndex = (focusedIndex + direction + navigationItems.length) % navigationItems.length;
53319
53717
  setFocusedIndex(newIndex);
53320
- const item = navigationItems[newIndex];
53321
- if (item && item.type === "model") {
53322
- onSelectModel(item.model);
53323
- }
53324
- return true;
53325
- }
53326
- if (key.name === "backspace") {
53327
- setSearchQuery((prev) => prev.slice(0, -1));
53328
53718
  return true;
53329
53719
  }
53330
53720
  if (key.name === "escape") {
53331
53721
  if (searchQuery) {
53332
53722
  setSearchQuery("");
53723
+ setFocusedIndex(0);
53333
53724
  return true;
53334
53725
  }
53335
53726
  if (onCancel) {
@@ -53338,16 +53729,16 @@ function ModelPicker({
53338
53729
  }
53339
53730
  return false;
53340
53731
  }
53341
- if (key.name === "space" && onReasoningToggle && thinkingSupported) {
53732
+ const currentItem = navigationItems[focusedIndex];
53733
+ if (key.name === "space" && currentItem?.type === "reasoning" && onReasoningToggle && thinkingSupported) {
53342
53734
  onReasoningToggle(!reasoningEnabled);
53343
53735
  return true;
53344
53736
  }
53345
- if (key.name === "space" && onOpenAIReasoningEffortChange && openAIReasoningSupported) {
53737
+ if (key.name === "space" && currentItem?.type === "openai-reasoning" && onOpenAIReasoningEffortChange && openAIReasoningSupported) {
53346
53738
  cycleOpenAIReasoningEffort();
53347
53739
  return true;
53348
53740
  }
53349
53741
  if (key.name === "return") {
53350
- const currentItem = navigationItems[focusedIndex];
53351
53742
  if (!currentItem)
53352
53743
  return false;
53353
53744
  if (currentItem.type === "local-input") {
@@ -53369,17 +53760,20 @@ function ModelPicker({
53369
53760
  }
53370
53761
  return next;
53371
53762
  });
53372
- } else {
53763
+ } else if (currentItem.type === "model" || currentItem.type === "recent-model") {
53764
+ onSelectModel(currentItem.model);
53373
53765
  onConfirm?.();
53374
53766
  }
53375
53767
  return true;
53376
53768
  }
53377
53769
  if (key.name === "left" || key.name === "right") {
53378
- const currentItem = navigationItems[focusedIndex];
53379
- if (!currentItem || currentItem.type === "reasoning" || currentItem.type === "openai-reasoning") {
53770
+ if (isSearching)
53771
+ return false;
53772
+ const currentItem2 = navigationItems[focusedIndex];
53773
+ if (!currentItem2 || currentItem2.type === "recent-model" || currentItem2.type === "reasoning" || currentItem2.type === "openai-reasoning") {
53380
53774
  return false;
53381
53775
  }
53382
- const targetProvider = currentItem.type === "provider" ? currentItem.provider : currentItem.type === "model" ? currentItem.model.provider : "local";
53776
+ const targetProvider = currentItem2.type === "provider" ? currentItem2.provider : currentItem2.type === "model" ? currentItem2.model.provider : "local";
53383
53777
  if (key.name === "left") {
53384
53778
  setExpandedProviders((prev) => {
53385
53779
  const next = new Set(prev);
@@ -53395,13 +53789,6 @@ function ModelPicker({
53395
53789
  }
53396
53790
  return true;
53397
53791
  }
53398
- if (key.sequence && key.sequence.length === 1 && /[a-zA-Z0-9\-_.]/.test(key.sequence)) {
53399
- setSearchQuery((prev) => prev + key.sequence);
53400
- if (!searchQuery) {
53401
- setExpandedProviders(new Set(providerOrder));
53402
- }
53403
- return true;
53404
- }
53405
53792
  return false;
53406
53793
  }, [
53407
53794
  focused,
@@ -53418,7 +53805,8 @@ function ModelPicker({
53418
53805
  thinkingSupported,
53419
53806
  onOpenAIReasoningEffortChange,
53420
53807
  openAIReasoningSupported,
53421
- cycleOpenAIReasoningEffort
53808
+ cycleOpenAIReasoningEffort,
53809
+ isSearching
53422
53810
  ]);
53423
53811
  useKeyboard((key) => {
53424
53812
  const handled = handleKeyboard(key);
@@ -53427,7 +53815,11 @@ function ModelPicker({
53427
53815
  }
53428
53816
  });
53429
53817
  const isProviderFocused = (provider) => navigationItems[focusedIndex]?.type === "provider" && navigationItems[focusedIndex].provider === provider;
53430
- const isModelFocused = (modelId) => navigationItems[focusedIndex]?.type === "model" && navigationItems[focusedIndex].model.id === modelId;
53818
+ const isModelFocused = (modelId, recent = false) => {
53819
+ const item = navigationItems[focusedIndex];
53820
+ const expectedType = recent ? "recent-model" : "model";
53821
+ return item?.type === expectedType && item.model.id === modelId;
53822
+ };
53431
53823
  const isLocalFieldFocused = (field) => navigationItems[focusedIndex]?.type === "local-input" && navigationItems[focusedIndex].field === field;
53432
53824
  return /* @__PURE__ */ jsxDEV4("box", {
53433
53825
  flexDirection: "column",
@@ -53438,21 +53830,42 @@ function ModelPicker({
53438
53830
  children: [
53439
53831
  /* @__PURE__ */ jsxDEV4("box", {
53440
53832
  flexShrink: 0,
53441
- children: searchQuery ? /* @__PURE__ */ jsxDEV4(PickerRow, {
53442
- children: /* @__PURE__ */ jsxDEV4("text", {
53443
- fg: colors2.text,
53833
+ marginBottom: 1,
53834
+ children: [
53835
+ /* @__PURE__ */ jsxDEV4(PickerRow, {
53444
53836
  children: [
53445
- "Search: ",
53446
- searchQuery
53837
+ /* @__PURE__ */ jsxDEV4("text", {
53838
+ fg: colors2.primary,
53839
+ children: "Search "
53840
+ }, undefined, false, undefined, this),
53841
+ /* @__PURE__ */ jsxDEV4("input", {
53842
+ flexGrow: 1,
53843
+ value: searchQuery,
53844
+ onInput: (value) => {
53845
+ setSearchQuery(value);
53846
+ setFocusedIndex(0);
53847
+ },
53848
+ focused: focused && editingLocalField === null,
53849
+ placeholder: "family, model name, or provider...",
53850
+ textColor: colors2.text,
53851
+ focusedTextColor: colors2.text,
53852
+ backgroundColor: "transparent",
53853
+ cursorColor: colors2.textMuted
53854
+ }, undefined, false, undefined, this)
53447
53855
  ]
53448
- }, undefined, true, undefined, this)
53449
- }, undefined, false, undefined, this) : /* @__PURE__ */ jsxDEV4(PickerRow, {
53450
- children: /* @__PURE__ */ jsxDEV4("text", {
53451
- fg: colors2.textMuted,
53452
- children: "Type to search models..."
53856
+ }, undefined, true, undefined, this),
53857
+ isSearching && /* @__PURE__ */ jsxDEV4(PickerRow, {
53858
+ children: /* @__PURE__ */ jsxDEV4("text", {
53859
+ fg: colors2.primary,
53860
+ children: [
53861
+ "Results (",
53862
+ filteredModels.length,
53863
+ ")"
53864
+ ]
53865
+ }, undefined, true, undefined, this)
53453
53866
  }, undefined, false, undefined, this)
53454
- }, undefined, false, undefined, this)
53455
- }, undefined, false, undefined, this),
53867
+ ]
53868
+ }, undefined, true, undefined, this),
53456
53869
  /* @__PURE__ */ jsxDEV4("scrollbox", {
53457
53870
  ref: scrollBoxRef,
53458
53871
  style: {
@@ -53460,170 +53873,240 @@ function ModelPicker({
53460
53873
  flexShrink: 1,
53461
53874
  width: "100%",
53462
53875
  overflow: "hidden",
53463
- maxHeight: navigationItems.length
53876
+ maxHeight: Math.max(1, navigationItems.length + (!isSearching && recentModels.length > 0 ? 1 : 0))
53464
53877
  },
53465
53878
  contentOptions: {
53466
53879
  flexDirection: "column"
53467
53880
  },
53468
53881
  scrollbarOptions: {
53469
- visible: false
53882
+ trackOptions: {
53883
+ foregroundColor: colors2.textMuted,
53884
+ backgroundColor: colors2.backgroundElement
53885
+ }
53470
53886
  }
53471
53887
  },
53472
53888
  stickyScroll: false,
53473
- children: providerOrder.flatMap((provider) => {
53474
- const isExpanded = expandedProviders.has(provider);
53475
- const providerName = providerNames[provider] || provider;
53476
- const isFocused = isProviderFocused(provider);
53477
- if (provider === "local") {
53478
- const localModels = groupedModels.local;
53479
- const modelCount = localModels?.length ?? 0;
53480
- const elements2 = [];
53481
- elements2.push(/* @__PURE__ */ jsxDEV4(PickerRow, {
53482
- id: "provider-local",
53889
+ children: [
53890
+ !isSearching && recentModels.length > 0 && /* @__PURE__ */ jsxDEV4(Fragment, {
53891
+ children: [
53892
+ /* @__PURE__ */ jsxDEV4(PickerRow, {
53893
+ children: /* @__PURE__ */ jsxDEV4("text", {
53894
+ fg: colors2.primary,
53895
+ children: "Recent"
53896
+ }, undefined, false, undefined, this)
53897
+ }, undefined, false, undefined, this),
53898
+ recentModels.map((model) => {
53899
+ const isSelected = model.id === selectedModel.id;
53900
+ const isFocused = isModelFocused(model.id, true);
53901
+ return /* @__PURE__ */ jsxDEV4(PickerRow, {
53902
+ id: `recent-model-${model.id}`,
53903
+ backgroundColor: isFocused ? colors2.backgroundSelected : undefined,
53904
+ children: /* @__PURE__ */ jsxDEV4("text", {
53905
+ children: [
53906
+ /* @__PURE__ */ jsxDEV4("span", {
53907
+ fg: isFocused ? colors2.primary : colors2.text,
53908
+ children: [
53909
+ isSelected ? "●" : "○",
53910
+ " ",
53911
+ model.name
53912
+ ]
53913
+ }, undefined, true, undefined, this),
53914
+ /* @__PURE__ */ jsxDEV4("span", {
53915
+ fg: colors2.textMuted,
53916
+ children: ` ${getProviderDisplayName(model.provider)}`
53917
+ }, undefined, false, undefined, this)
53918
+ ]
53919
+ }, undefined, true, undefined, this)
53920
+ }, `recent-${model.id}`, false, undefined, this);
53921
+ })
53922
+ ]
53923
+ }, undefined, true, undefined, this),
53924
+ isSearching && (filteredModels.length === 0 ? /* @__PURE__ */ jsxDEV4(PickerRow, {
53925
+ children: /* @__PURE__ */ jsxDEV4("text", {
53926
+ fg: colors2.textMuted,
53927
+ children: "No matching models"
53928
+ }, undefined, false, undefined, this)
53929
+ }, undefined, false, undefined, this) : filteredModels.map((model) => {
53930
+ const isSelected = model.id === selectedModel.id;
53931
+ const isFocused = isModelFocused(model.id);
53932
+ return /* @__PURE__ */ jsxDEV4(PickerRow, {
53933
+ id: `model-${model.id}`,
53934
+ backgroundColor: isFocused ? colors2.backgroundSelected : undefined,
53483
53935
  children: /* @__PURE__ */ jsxDEV4("text", {
53484
- fg: isFocused ? colors2.primary : isExpanded ? colors2.text : colors2.textMuted,
53485
53936
  children: [
53486
- isFocused ? "❯" : isExpanded ? "" : "▸",
53487
- " ",
53488
- providerName,
53489
- modelCount > 0 ? ` (${modelCount})` : ""
53937
+ /* @__PURE__ */ jsxDEV4("span", {
53938
+ fg: isFocused ? colors2.primary : colors2.text,
53939
+ children: [
53940
+ isSelected ? "●" : "",
53941
+ " ",
53942
+ model.name
53943
+ ]
53944
+ }, undefined, true, undefined, this),
53945
+ /* @__PURE__ */ jsxDEV4("span", {
53946
+ fg: colors2.textMuted,
53947
+ children: ` ${getProviderDisplayName(model.provider)}`
53948
+ }, undefined, false, undefined, this)
53490
53949
  ]
53491
53950
  }, undefined, true, undefined, this)
53492
- }, "local", false, undefined, this));
53493
- if (isExpanded) {
53494
- const isUrlFocused = isLocalFieldFocused("url");
53495
- const isUrlEditing = editingLocalField === "url";
53496
- if (isUrlEditing) {
53497
- elements2.push(/* @__PURE__ */ jsxDEV4(PickerRow, {
53498
- id: "local-input-url",
53499
- paddingLeft: 2,
53951
+ }, model.id, false, undefined, this);
53952
+ })),
53953
+ providerOrder.flatMap((provider) => {
53954
+ if (isSearching)
53955
+ return [];
53956
+ const isExpanded = expandedProviders.has(provider);
53957
+ const providerName = getProviderDisplayName(provider);
53958
+ const isFocused = isProviderFocused(provider);
53959
+ if (provider === "local") {
53960
+ const localModels = groupedModels.local;
53961
+ const modelCount = localModels?.length ?? 0;
53962
+ const elements2 = [];
53963
+ elements2.push(/* @__PURE__ */ jsxDEV4(PickerRow, {
53964
+ id: "provider-local",
53965
+ children: /* @__PURE__ */ jsxDEV4("text", {
53966
+ fg: isFocused ? colors2.primary : isExpanded ? colors2.text : colors2.textMuted,
53500
53967
  children: [
53501
- /* @__PURE__ */ jsxDEV4("text", {
53502
- fg: colors2.primary,
53503
- children: " URL: "
53504
- }, undefined, false, undefined, this),
53505
- /* @__PURE__ */ jsxDEV4("input", {
53506
- focused: true,
53507
- value: localUrl,
53508
- backgroundColor: "transparent",
53509
- cursorColor: colors2.textMuted,
53510
- onInput: (v) => setLocalUrl(typeof v === "string" ? v : ""),
53511
- onPaste: (event) => {
53512
- const cleaned = getPasteText(event).replace(/\r?\n/g, "");
53513
- setLocalUrl((prev) => `${prev}${cleaned}`);
53514
- },
53515
- onSubmit: finishEditing
53516
- }, undefined, false, undefined, this)
53968
+ isFocused ? "❯" : isExpanded ? "" : "▸",
53969
+ " ",
53970
+ providerName,
53971
+ modelCount > 0 ? ` (${modelCount})` : ""
53517
53972
  ]
53518
- }, "local-url", true, undefined, this));
53519
- } else {
53520
- elements2.push(/* @__PURE__ */ jsxDEV4(PickerRow, {
53521
- id: "local-input-url",
53522
- paddingLeft: 2,
53523
- children: /* @__PURE__ */ jsxDEV4("text", {
53524
- fg: isUrlFocused ? colors2.primary : colors2.textMuted,
53525
- children: ` URL: ${localUrl || "(press Enter to set)"}`
53526
- }, undefined, false, undefined, this)
53527
- }, "local-url", false, undefined, this));
53528
- }
53529
- const isModelFieldFocused = isLocalFieldFocused("model");
53530
- const isModelEditing = editingLocalField === "model";
53531
- if (isModelEditing) {
53532
- elements2.push(/* @__PURE__ */ jsxDEV4(PickerRow, {
53533
- id: "local-input-model",
53534
- paddingLeft: 2,
53535
- children: [
53536
- /* @__PURE__ */ jsxDEV4("text", {
53537
- fg: colors2.primary,
53538
- children: " Model: "
53539
- }, undefined, false, undefined, this),
53540
- /* @__PURE__ */ jsxDEV4("input", {
53541
- focused: true,
53542
- value: localModelName,
53543
- backgroundColor: "transparent",
53544
- cursorColor: colors2.textMuted,
53545
- onInput: (v) => setLocalModelName(typeof v === "string" ? v : ""),
53546
- onPaste: (event) => {
53547
- const cleaned = getPasteText(event).replace(/\r?\n/g, "");
53548
- setLocalModelName((prev) => `${prev}${cleaned}`);
53549
- },
53550
- onSubmit: finishEditing
53973
+ }, undefined, true, undefined, this)
53974
+ }, "local", false, undefined, this));
53975
+ if (isExpanded) {
53976
+ const isUrlFocused = isLocalFieldFocused("url");
53977
+ const isUrlEditing = editingLocalField === "url";
53978
+ if (isUrlEditing) {
53979
+ elements2.push(/* @__PURE__ */ jsxDEV4(PickerRow, {
53980
+ id: "local-input-url",
53981
+ paddingLeft: 2,
53982
+ children: [
53983
+ /* @__PURE__ */ jsxDEV4("text", {
53984
+ fg: colors2.primary,
53985
+ children: " URL: "
53986
+ }, undefined, false, undefined, this),
53987
+ /* @__PURE__ */ jsxDEV4("input", {
53988
+ focused: true,
53989
+ value: localUrl,
53990
+ backgroundColor: "transparent",
53991
+ cursorColor: colors2.textMuted,
53992
+ onInput: (v) => setLocalUrl(typeof v === "string" ? v : ""),
53993
+ onPaste: (event) => {
53994
+ const cleaned = getPasteText(event).replace(/\r?\n/g, "");
53995
+ setLocalUrl((prev) => `${prev}${cleaned}`);
53996
+ },
53997
+ onSubmit: finishEditing
53998
+ }, undefined, false, undefined, this)
53999
+ ]
54000
+ }, "local-url", true, undefined, this));
54001
+ } else {
54002
+ elements2.push(/* @__PURE__ */ jsxDEV4(PickerRow, {
54003
+ id: "local-input-url",
54004
+ paddingLeft: 2,
54005
+ children: /* @__PURE__ */ jsxDEV4("text", {
54006
+ fg: isUrlFocused ? colors2.primary : colors2.textMuted,
54007
+ children: ` URL: ${localUrl || "(press Enter to set)"}`
53551
54008
  }, undefined, false, undefined, this)
53552
- ]
53553
- }, "local-model", true, undefined, this));
53554
- } else {
53555
- elements2.push(/* @__PURE__ */ jsxDEV4(PickerRow, {
53556
- id: "local-input-model",
53557
- paddingLeft: 2,
53558
- children: /* @__PURE__ */ jsxDEV4("text", {
53559
- fg: isModelFieldFocused ? colors2.primary : colors2.textMuted,
53560
- children: ` Model: ${localModelName || "(press Enter to set)"}`
53561
- }, undefined, false, undefined, this)
53562
- }, "local-model", false, undefined, this));
53563
- }
53564
- if (localModels) {
53565
- for (const m of localModels) {
53566
- const isSelected = m.id === selectedModel.id;
53567
- const isMFocused = isModelFocused(m.id);
54009
+ }, "local-url", false, undefined, this));
54010
+ }
54011
+ const isModelFieldFocused = isLocalFieldFocused("model");
54012
+ const isModelEditing = editingLocalField === "model";
54013
+ if (isModelEditing) {
54014
+ elements2.push(/* @__PURE__ */ jsxDEV4(PickerRow, {
54015
+ id: "local-input-model",
54016
+ paddingLeft: 2,
54017
+ children: [
54018
+ /* @__PURE__ */ jsxDEV4("text", {
54019
+ fg: colors2.primary,
54020
+ children: " Model: "
54021
+ }, undefined, false, undefined, this),
54022
+ /* @__PURE__ */ jsxDEV4("input", {
54023
+ focused: true,
54024
+ value: localModelName,
54025
+ backgroundColor: "transparent",
54026
+ cursorColor: colors2.textMuted,
54027
+ onInput: (v) => setLocalModelName(typeof v === "string" ? v : ""),
54028
+ onPaste: (event) => {
54029
+ const cleaned = getPasteText(event).replace(/\r?\n/g, "");
54030
+ setLocalModelName((prev) => `${prev}${cleaned}`);
54031
+ },
54032
+ onSubmit: finishEditing
54033
+ }, undefined, false, undefined, this)
54034
+ ]
54035
+ }, "local-model", true, undefined, this));
54036
+ } else {
53568
54037
  elements2.push(/* @__PURE__ */ jsxDEV4(PickerRow, {
53569
- id: `model-${m.id}`,
54038
+ id: "local-input-model",
53570
54039
  paddingLeft: 2,
53571
54040
  children: /* @__PURE__ */ jsxDEV4("text", {
53572
- fg: isMFocused ? colors2.primary : colors2.textMuted,
53573
- children: [
53574
- isSelected ? "●" : "○",
53575
- " ",
53576
- m.name
53577
- ]
53578
- }, undefined, true, undefined, this)
53579
- }, m.id, false, undefined, this));
54041
+ fg: isModelFieldFocused ? colors2.primary : colors2.textMuted,
54042
+ children: ` Model: ${localModelName || "(press Enter to set)"}`
54043
+ }, undefined, false, undefined, this)
54044
+ }, "local-model", false, undefined, this));
54045
+ }
54046
+ if (localModels) {
54047
+ for (const m of localModels) {
54048
+ const isSelected = m.id === selectedModel.id;
54049
+ const isMFocused = isModelFocused(m.id);
54050
+ elements2.push(/* @__PURE__ */ jsxDEV4(PickerRow, {
54051
+ id: `model-${m.id}`,
54052
+ paddingLeft: 2,
54053
+ children: /* @__PURE__ */ jsxDEV4("text", {
54054
+ fg: isMFocused ? colors2.primary : colors2.textMuted,
54055
+ children: [
54056
+ isSelected ? "●" : "○",
54057
+ " ",
54058
+ m.name
54059
+ ]
54060
+ }, undefined, true, undefined, this)
54061
+ }, m.id, false, undefined, this));
54062
+ }
53580
54063
  }
53581
54064
  }
54065
+ return elements2;
53582
54066
  }
53583
- return elements2;
53584
- }
53585
- const models = groupedModels[provider];
53586
- if (!models || models.length === 0)
53587
- return [];
53588
- const elements = [];
53589
- elements.push(/* @__PURE__ */ jsxDEV4(PickerRow, {
53590
- id: `provider-${provider}`,
53591
- children: /* @__PURE__ */ jsxDEV4("text", {
53592
- fg: isFocused ? colors2.primary : isExpanded ? colors2.text : colors2.textMuted,
53593
- children: [
53594
- isFocused ? "❯" : isExpanded ? "▾" : "▸",
53595
- " ",
53596
- providerName,
53597
- " (",
53598
- models.length,
53599
- ")"
53600
- ]
53601
- }, undefined, true, undefined, this)
53602
- }, provider, false, undefined, this));
53603
- if (isExpanded) {
53604
- for (const m of models) {
53605
- const isSelected = m.id === selectedModel.id;
53606
- const isMFocused = isModelFocused(m.id);
53607
- const isDefault = m.id === "claude-haiku-4-5" || m.id === "gpt-4o-mini";
53608
- elements.push(/* @__PURE__ */ jsxDEV4(PickerRow, {
53609
- id: `model-${m.id}`,
53610
- paddingLeft: 2,
53611
- children: /* @__PURE__ */ jsxDEV4("text", {
53612
- fg: isMFocused ? colors2.primary : colors2.textMuted,
53613
- children: [
53614
- isSelected ? "●" : "○",
53615
- " ",
53616
- m.name,
53617
- isDefault && !isModelUserSelected && isSelected ? " [default]" : ""
53618
- ]
53619
- }, undefined, true, undefined, this)
53620
- }, m.id, false, undefined, this));
54067
+ const models = groupedModels[provider];
54068
+ if (!models || models.length === 0)
54069
+ return [];
54070
+ const elements = [];
54071
+ elements.push(/* @__PURE__ */ jsxDEV4(PickerRow, {
54072
+ id: `provider-${provider}`,
54073
+ children: /* @__PURE__ */ jsxDEV4("text", {
54074
+ fg: isFocused ? colors2.primary : isExpanded ? colors2.text : colors2.textMuted,
54075
+ children: [
54076
+ isFocused ? "❯" : isExpanded ? "▾" : "▸",
54077
+ " ",
54078
+ providerName,
54079
+ " (",
54080
+ models.length,
54081
+ ")"
54082
+ ]
54083
+ }, undefined, true, undefined, this)
54084
+ }, provider, false, undefined, this));
54085
+ if (isExpanded) {
54086
+ for (const m of models) {
54087
+ const isSelected = m.id === selectedModel.id;
54088
+ const isMFocused = isModelFocused(m.id);
54089
+ const isDefault = m.id === "claude-haiku-4-5" || m.id === "gpt-4o-mini";
54090
+ elements.push(/* @__PURE__ */ jsxDEV4(PickerRow, {
54091
+ id: `model-${m.id}`,
54092
+ paddingLeft: 2,
54093
+ children: /* @__PURE__ */ jsxDEV4("text", {
54094
+ fg: isMFocused ? colors2.primary : colors2.textMuted,
54095
+ children: [
54096
+ isSelected ? "●" : "○",
54097
+ " ",
54098
+ m.name,
54099
+ isDefault && !isModelUserSelected && isSelected ? " [default]" : ""
54100
+ ]
54101
+ }, undefined, true, undefined, this)
54102
+ }, m.id, false, undefined, this));
54103
+ }
53621
54104
  }
53622
- }
53623
- return elements;
53624
- })
53625
- }, undefined, false, undefined, this),
53626
- onReasoningToggle && thinkingSupported && /* @__PURE__ */ jsxDEV4("box", {
54105
+ return elements;
54106
+ })
54107
+ ]
54108
+ }, isSearching ? "search-results" : "model-browser", true, undefined, this),
54109
+ !isSearching && onReasoningToggle && thinkingSupported && /* @__PURE__ */ jsxDEV4("box", {
53627
54110
  flexShrink: 0,
53628
54111
  paddingTop: 1,
53629
54112
  children: /* @__PURE__ */ jsxDEV4(PickerRow, {
@@ -53637,7 +54120,7 @@ function ModelPicker({
53637
54120
  }, undefined, true, undefined, this)
53638
54121
  }, undefined, false, undefined, this)
53639
54122
  }, undefined, false, undefined, this),
53640
- onOpenAIReasoningEffortChange && openAIReasoningSupported && /* @__PURE__ */ jsxDEV4("box", {
54123
+ !isSearching && onOpenAIReasoningEffortChange && openAIReasoningSupported && /* @__PURE__ */ jsxDEV4("box", {
53641
54124
  flexShrink: 0,
53642
54125
  paddingTop: 1,
53643
54126
  children: /* @__PURE__ */ jsxDEV4(PickerRow, {
@@ -53737,7 +54220,7 @@ function ConfigView({ config: config2, onBack, onStart }) {
53737
54220
  return;
53738
54221
  }
53739
54222
  });
53740
- const handleModelSelect = useCallback4((model) => {
54223
+ const handleModelSelect = useCallback6((model) => {
53741
54224
  setSelectedModel(model);
53742
54225
  setIsModelUserSelected(true);
53743
54226
  }, []);
@@ -53913,7 +54396,7 @@ function isValidUrl(url) {
53913
54396
  }
53914
54397
 
53915
54398
  // src/tui/components/chat/home-view.tsx
53916
- import { useCallback as useCallback11, useEffect as useEffect11, useState as useState18 } from "react";
54399
+ import { useCallback as useCallback13, useEffect as useEffect11, useState as useState18 } from "react";
53917
54400
 
53918
54401
  // src/core/history.ts
53919
54402
  init_storage();
@@ -53970,7 +54453,7 @@ function getEntries() {
53970
54453
  // src/tui/context/command.tsx
53971
54454
  import {
53972
54455
  createContext as createContext7,
53973
- useCallback as useCallback6,
54456
+ useCallback as useCallback8,
53974
54457
  useContext as useContext7,
53975
54458
  useEffect as useEffect5,
53976
54459
  useMemo as useMemo8,
@@ -54365,7 +54848,7 @@ function deobfuscate(input) {
54365
54848
  return output;
54366
54849
  }
54367
54850
  // src/tui/command-registry.ts
54368
- init_config2();
54851
+ init_config();
54369
54852
 
54370
54853
  // src/tui/utils/command-flags.ts
54371
54854
  import { readFileSync } from "node:fs";
@@ -55023,8 +55506,8 @@ var commands = [
55023
55506
  aliases: ["quit", "q"],
55024
55507
  description: "Exit the application",
55025
55508
  category: "General",
55026
- handler: async () => {
55027
- process.kill(process.pid, "SIGINT");
55509
+ handler: async (_args, ctx2) => {
55510
+ await ctx2.exitApplication?.();
55028
55511
  }
55029
55512
  },
55030
55513
  {
@@ -55268,7 +55751,7 @@ var useRoute = () => {
55268
55751
  // src/tui/context/toast.tsx
55269
55752
  import {
55270
55753
  createContext as createContext6,
55271
- useCallback as useCallback5,
55754
+ useCallback as useCallback7,
55272
55755
  useContext as useContext6,
55273
55756
  useMemo as useMemo7,
55274
55757
  useState as useState8
@@ -55283,10 +55766,10 @@ var DEFAULT_DURATION = {
55283
55766
  };
55284
55767
  function ToastProvider({ children }) {
55285
55768
  const [toasts, setToasts] = useState8([]);
55286
- const dismiss = useCallback5((id) => {
55769
+ const dismiss = useCallback7((id) => {
55287
55770
  setToasts((prev) => prev.filter((t) => t.id !== id));
55288
55771
  }, []);
55289
- const toast = useCallback5((message, variant = "default", duration) => {
55772
+ const toast = useCallback7((message, variant = "default", duration) => {
55290
55773
  const id = nextId++;
55291
55774
  const ms = duration ?? DEFAULT_DURATION[variant];
55292
55775
  setToasts((prev) => [...prev, { id, message, variant, duration: ms }]);
@@ -55309,11 +55792,11 @@ function useToast() {
55309
55792
  import { jsxDEV as jsxDEV9 } from "@opentui/react/jsx-dev-runtime";
55310
55793
  var CommandContext = createContext7(null);
55311
55794
  function useCommand() {
55312
- const context = useContext7(CommandContext);
55313
- if (!context) {
55795
+ const context2 = useContext7(CommandContext);
55796
+ if (!context2) {
55314
55797
  throw new Error("useCommand must be used within CommandProvider");
55315
55798
  }
55316
- return context;
55799
+ return context2;
55317
55800
  }
55318
55801
  function CommandProvider({
55319
55802
  children,
@@ -55326,7 +55809,8 @@ function CommandProvider({
55326
55809
  onOpenHelpDialog,
55327
55810
  onOpenAuthDialog,
55328
55811
  onOpenPentestDialog,
55329
- onOpenSkillsDialog
55812
+ onOpenSkillsDialog,
55813
+ onExit
55330
55814
  }) {
55331
55815
  const route = useRoute();
55332
55816
  const obfuscation = useObfuscation();
@@ -55347,6 +55831,7 @@ function CommandProvider({
55347
55831
  openAuthDialog: onOpenAuthDialog,
55348
55832
  openPentestDialog: onOpenPentestDialog,
55349
55833
  openSkillsDialog: onOpenSkillsDialog,
55834
+ exitApplication: onExit,
55350
55835
  setObfuscation: (enabled) => {
55351
55836
  const current = isObfuscationEnabled();
55352
55837
  const next = typeof enabled === "boolean" ? enabled : !current;
@@ -55369,10 +55854,11 @@ function CommandProvider({
55369
55854
  onOpenAuthDialog,
55370
55855
  onOpenPentestDialog,
55371
55856
  onOpenSkillsDialog,
55857
+ onExit,
55372
55858
  obfuscation,
55373
55859
  toast
55374
55860
  ]);
55375
- const refreshSkills = useCallback6(async () => {
55861
+ const refreshSkills = useCallback8(async () => {
55376
55862
  await registry.refresh();
55377
55863
  setRegistryVersion((v) => v + 1);
55378
55864
  }, [registry]);
@@ -55388,7 +55874,7 @@ function CommandProvider({
55388
55874
  }
55389
55875
  return router2;
55390
55876
  }, []);
55391
- const resolveSkillContent = useCallback6((input) => {
55877
+ const resolveSkillContent = useCallback8((input) => {
55392
55878
  const trimmed = input.trim().replace(/^\/+/, "").toLowerCase();
55393
55879
  const entry = registry.get(trimmed);
55394
55880
  if (entry) {
@@ -55458,7 +55944,7 @@ function CommandProvider({
55458
55944
  }
55459
55945
  return names;
55460
55946
  }, []);
55461
- const executeCommand = useCallback6(async (input) => {
55947
+ const executeCommand = useCallback8(async (input) => {
55462
55948
  return await router.execute(input, ctx3);
55463
55949
  }, [router, ctx3]);
55464
55950
  const value = useMemo8(() => ({
@@ -55493,7 +55979,7 @@ function CommandProvider({
55493
55979
  import { useKeyboard as useKeyboard3, useRenderer as useRenderer3 } from "@opentui/react";
55494
55980
  import {
55495
55981
  createContext as createContext9,
55496
- useCallback as useCallback7,
55982
+ useCallback as useCallback9,
55497
55983
  useContext as useContext9,
55498
55984
  useRef as useRef4,
55499
55985
  useState as useState10
@@ -55578,7 +56064,7 @@ function DialogProvider({ children }) {
55578
56064
  const [externalDialogOpen, setExternalDialogOpen] = useState10(false);
55579
56065
  const renderer = useRenderer3();
55580
56066
  const focusRef = useRef4(null);
55581
- const refocus = useCallback7(() => {
56067
+ const refocus = useCallback9(() => {
55582
56068
  setTimeout(() => {
55583
56069
  const focus = focusRef.current;
55584
56070
  if (!focus)
@@ -55600,7 +56086,7 @@ function DialogProvider({ children }) {
55600
56086
  focus.focus();
55601
56087
  }, 1);
55602
56088
  }, [renderer]);
55603
- const clear = useCallback7(() => {
56089
+ const clear = useCallback9(() => {
55604
56090
  for (const item of stack) {
55605
56091
  if (item.onClose)
55606
56092
  item.onClose();
@@ -55609,7 +56095,7 @@ function DialogProvider({ children }) {
55609
56095
  setStack([]);
55610
56096
  refocus();
55611
56097
  }, [stack, refocus]);
55612
- const replace = useCallback7((element, options) => {
56098
+ const replace = useCallback9((element, options) => {
55613
56099
  if (stack.length === 0) {
55614
56100
  focusRef.current = renderer.currentFocusedRenderable;
55615
56101
  }
@@ -55673,7 +56159,7 @@ function useDialog() {
55673
56159
  // src/tui/context/focus.tsx
55674
56160
  import {
55675
56161
  createContext as createContext10,
55676
- useCallback as useCallback8,
56162
+ useCallback as useCallback10,
55677
56163
  useContext as useContext10,
55678
56164
  useRef as useRef5
55679
56165
  } from "react";
@@ -55681,17 +56167,17 @@ import { jsxDEV as jsxDEV12 } from "@opentui/react/jsx-dev-runtime";
55681
56167
  var FocusContext = createContext10(undefined);
55682
56168
  function FocusProvider({ children }) {
55683
56169
  const promptRef = useRef5(null);
55684
- const refocusPrompt = useCallback8(() => {
56170
+ const refocusPrompt = useCallback10(() => {
55685
56171
  setTimeout(() => {
55686
56172
  promptRef.current?.focus();
55687
56173
  }, 1);
55688
56174
  }, []);
55689
- const focusPrompt = useCallback8(() => promptRef.current?.focus(), []);
55690
- const blurPrompt = useCallback8(() => promptRef.current?.blur(), []);
55691
- const resetPrompt = useCallback8(() => promptRef.current?.reset(), []);
55692
- const setPromptValue = useCallback8((value) => promptRef.current?.setValue(value), []);
55693
- const getPromptValue = useCallback8(() => promptRef.current?.getValue() ?? "", []);
55694
- const registerPromptRef = useCallback8((ref) => {
56175
+ const focusPrompt = useCallback10(() => promptRef.current?.focus(), []);
56176
+ const blurPrompt = useCallback10(() => promptRef.current?.blur(), []);
56177
+ const resetPrompt = useCallback10(() => promptRef.current?.reset(), []);
56178
+ const setPromptValue = useCallback10((value) => promptRef.current?.setValue(value), []);
56179
+ const getPromptValue = useCallback10(() => promptRef.current?.getValue() ?? "", []);
56180
+ const registerPromptRef = useCallback10((ref) => {
55695
56181
  promptRef.current = ref;
55696
56182
  }, []);
55697
56183
  return /* @__PURE__ */ jsxDEV12(FocusContext.Provider, {
@@ -55709,17 +56195,17 @@ function FocusProvider({ children }) {
55709
56195
  }, undefined, false, undefined, this);
55710
56196
  }
55711
56197
  function useFocus() {
55712
- const context = useContext10(FocusContext);
55713
- if (!context) {
56198
+ const context2 = useContext10(FocusContext);
56199
+ if (!context2) {
55714
56200
  throw new Error("useFocus must be used within FocusProvider");
55715
56201
  }
55716
- return context;
56202
+ return context2;
55717
56203
  }
55718
56204
 
55719
56205
  // src/tui/context/input.tsx
55720
56206
  import {
55721
56207
  createContext as createContext11,
55722
- useCallback as useCallback9,
56208
+ useCallback as useCallback11,
55723
56209
  useContext as useContext11,
55724
56210
  useState as useState11
55725
56211
  } from "react";
@@ -55727,7 +56213,7 @@ import { jsxDEV as jsxDEV13 } from "@opentui/react/jsx-dev-runtime";
55727
56213
  var InputContext = createContext11(undefined);
55728
56214
  function InputProvider({ children }) {
55729
56215
  const [inputValue, setInputValue] = useState11("");
55730
- const clearInput = useCallback9(() => {
56216
+ const clearInput = useCallback11(() => {
55731
56217
  setInputValue("");
55732
56218
  }, []);
55733
56219
  return /* @__PURE__ */ jsxDEV13(InputContext.Provider, {
@@ -55741,11 +56227,11 @@ function InputProvider({ children }) {
55741
56227
  }, undefined, false, undefined, this);
55742
56228
  }
55743
56229
  function useInput() {
55744
- const context = useContext11(InputContext);
55745
- if (!context) {
56230
+ const context2 = useContext11(InputContext);
56231
+ if (!context2) {
55746
56232
  throw new Error("useInput must be used within InputProvider");
55747
56233
  }
55748
- return context;
56234
+ return context2;
55749
56235
  }
55750
56236
 
55751
56237
  // src/tui/components/shared/tool-registry.ts
@@ -57371,7 +57857,7 @@ function markdownToStyledText(rawContent, colors2) {
57371
57857
  import {
57372
57858
  SyntaxStyle
57373
57859
  } from "@opentui/core";
57374
- import { useCallback as useCallback10, useMemo as useMemo9, useRef as useRef6 } from "react";
57860
+ import { useCallback as useCallback12, useMemo as useMemo9, useRef as useRef6 } from "react";
57375
57861
  function useMarkdownSyntaxStyle() {
57376
57862
  const { colors: colors2 } = useTheme();
57377
57863
  return useMemo9(() => SyntaxStyle.fromStyles({
@@ -57392,7 +57878,7 @@ function useMarkdownSyntaxStyle() {
57392
57878
  }
57393
57879
  function useMarkdownRenderNode() {
57394
57880
  const { colors: colors2 } = useTheme();
57395
- return useCallback10((token, ctx3) => {
57881
+ return useCallback12((token, ctx3) => {
57396
57882
  if (token.type === "code") {
57397
57883
  const renderable = ctx3.defaultRender();
57398
57884
  if (renderable) {
@@ -58780,7 +59266,7 @@ function isToolMessage(msg) {
58780
59266
  }
58781
59267
 
58782
59268
  // src/tui/components/shared/tool-renderer.tsx
58783
- import { jsxDEV as jsxDEV19, Fragment } from "@opentui/react/jsx-dev-runtime";
59269
+ import { jsxDEV as jsxDEV19, Fragment as Fragment2 } from "@opentui/react/jsx-dev-runtime";
58784
59270
  var TOOLS_WITH_LOG_WINDOW = new Set([
58785
59271
  "execute_command",
58786
59272
  "run_attack_surface",
@@ -58843,7 +59329,7 @@ var ToolRenderer = memo2(function ToolRenderer2({
58843
59329
  children: isPending ? /* @__PURE__ */ jsxDEV19(ShiningText, {
58844
59330
  text: summary,
58845
59331
  fg: colors2.warning
58846
- }, undefined, false, undefined, this) : /* @__PURE__ */ jsxDEV19(Fragment, {
59332
+ }, undefined, false, undefined, this) : /* @__PURE__ */ jsxDEV19(Fragment2, {
58847
59333
  children: [
58848
59334
  /* @__PURE__ */ jsxDEV19("text", {
58849
59335
  fg: isError ? colors2.error : colors2.success,
@@ -59990,7 +60476,7 @@ var PromptInput = forwardRef(function PromptInput2({
59990
60476
  import { useKeyboard as useKeyboard5 } from "@opentui/react";
59991
60477
  import { useEffect as useEffect9, useRef as useRef9, useState as useState16 } from "react";
59992
60478
  import { jsxDEV as jsxDEV22 } from "@opentui/react/jsx-dev-runtime";
59993
- var PROVIDER_DISPLAY_NAMES = {
60479
+ var PROVIDER_DISPLAY_NAMES2 = {
59994
60480
  anthropic: "Anthropic",
59995
60481
  openai: "OpenAI",
59996
60482
  google: "Google",
@@ -60001,7 +60487,7 @@ var PROVIDER_DISPLAY_NAMES = {
60001
60487
  inception: "Inception",
60002
60488
  local: "Local"
60003
60489
  };
60004
- var providerDisplayName = (p) => PROVIDER_DISPLAY_NAMES[p] ?? p;
60490
+ var providerDisplayName = (p) => PROVIDER_DISPLAY_NAMES2[p] ?? p;
60005
60491
  function OperatorModeBar({
60006
60492
  operatorMode,
60007
60493
  modelName,
@@ -60582,7 +61068,7 @@ function HomeView({ onNavigate, onStartSession }) {
60582
61068
  useEffect11(() => {
60583
61069
  load().then(setCommandHistory);
60584
61070
  }, []);
60585
- const launchOperator = useCallback11((message, options) => {
61071
+ const launchOperator = useCallback13((message, options) => {
60586
61072
  const requireApproval = options?.requireApproval ?? true;
60587
61073
  route.navigate({
60588
61074
  type: "operator",
@@ -60593,10 +61079,10 @@ function HomeView({ onNavigate, onStartSession }) {
60593
61079
  }
60594
61080
  });
60595
61081
  }, [route]);
60596
- const pushHistory = useCallback11((entry) => {
61082
+ const pushHistory = useCallback13((entry) => {
60597
61083
  push(entry).then(() => setCommandHistory([...getEntries()]));
60598
61084
  }, []);
60599
- const handleSubmit = useCallback11((value) => {
61085
+ const handleSubmit = useCallback13((value) => {
60600
61086
  if (!value.trim())
60601
61087
  return;
60602
61088
  pushHistory(value.trim());
@@ -60608,7 +61094,7 @@ function HomeView({ onNavigate, onStartSession }) {
60608
61094
  const timer = setTimeout(() => setHintMessage(null), 3000);
60609
61095
  return () => clearTimeout(timer);
60610
61096
  }, [hintMessage]);
60611
- const handleCommandExecute = useCallback11(async (command) => {
61097
+ const handleCommandExecute = useCallback13(async (command) => {
60612
61098
  const trimmed = command.trim();
60613
61099
  pushHistory(trimmed);
60614
61100
  const handled = await executeCommand(command);
@@ -60805,7 +61291,7 @@ function ChatApp({
60805
61291
  });
60806
61292
  }
60807
61293
  }, [initialSessionId]);
60808
- const handleNavigate = useCallback12((view, options) => {
61294
+ const handleNavigate = useCallback14((view, options) => {
60809
61295
  if (view === "chat" && options?.sessionId) {
60810
61296
  sessions.get(options.sessionId).then((session) => {
60811
61297
  if (session) {
@@ -60817,7 +61303,7 @@ function ChatApp({
60817
61303
  setCurrentView(view);
60818
61304
  }
60819
61305
  }, []);
60820
- const handleStartSession = useCallback12(async (directive) => {
61306
+ const handleStartSession = useCallback14(async (directive) => {
60821
61307
  const session = await sessions.create({
60822
61308
  name: `exploration-${Date.now()}`,
60823
61309
  targets: []
@@ -60826,7 +61312,7 @@ function ChatApp({
60826
61312
  setInitialDirective(directive);
60827
61313
  setCurrentView("chat");
60828
61314
  }, []);
60829
- const handleConfigSubmit = useCallback12(async (configData) => {
61315
+ const handleConfigSubmit = useCallback14(async (configData) => {
60830
61316
  const hostname = parseHostFromTarget(configData.targetUrl);
60831
61317
  const timestamp = new Date().toISOString().split("T")[0];
60832
61318
  const sessionName = `${hostname}-${timestamp}`;
@@ -60849,12 +61335,12 @@ function ChatApp({
60849
61335
  setSessionModel(configData.model);
60850
61336
  setCurrentView("chat");
60851
61337
  }, []);
60852
- const handleExitChat = useCallback12(() => {
61338
+ const handleExitChat = useCallback14(() => {
60853
61339
  setCurrentView("home");
60854
61340
  setActiveSession(null);
60855
61341
  setInitialDirective(undefined);
60856
61342
  }, []);
60857
- const handleBackFromConfig = useCallback12(() => {
61343
+ const handleBackFromConfig = useCallback14(() => {
60858
61344
  setCurrentView("home");
60859
61345
  }, []);
60860
61346
  return /* @__PURE__ */ jsxDEV25("box", {
@@ -60887,7 +61373,7 @@ function parseHostFromTarget(target) {
60887
61373
 
60888
61374
  // src/tui/components/commands/advanced-settings.tsx
60889
61375
  import { useKeyboard as useKeyboard6 } from "@opentui/react";
60890
- import { useCallback as useCallback13, useState as useState20 } from "react";
61376
+ import { useCallback as useCallback15, useState as useState20 } from "react";
60891
61377
 
60892
61378
  // src/tui/components/dialog-layout.tsx
60893
61379
  import { jsxDEV as jsxDEV26 } from "@opentui/react/jsx-dev-runtime";
@@ -60959,7 +61445,7 @@ function AdvancedSettings({ onClose }) {
60959
61445
  const [saving, setSaving] = useState20(false);
60960
61446
  const [error, setError] = useState20(null);
60961
61447
  const [draftStrikeMode, setDraftStrikeMode] = useState20(config2.data.strikeMode ?? false);
60962
- const saveSettings = useCallback13(async () => {
61448
+ const saveSettings = useCallback15(async () => {
60963
61449
  if (saving)
60964
61450
  return;
60965
61451
  setSaving(true);
@@ -61041,9 +61527,9 @@ function AdvancedSettings({ onClose }) {
61041
61527
 
61042
61528
  // src/tui/components/commands/auth-flow.tsx
61043
61529
  import { useKeyboard as useKeyboard7 } from "@opentui/react";
61044
- import { useCallback as useCallback14, useEffect as useEffect13, useRef as useRef11, useState as useState21 } from "react";
61530
+ import { useCallback as useCallback16, useEffect as useEffect13, useRef as useRef11, useState as useState21 } from "react";
61045
61531
  init_auth();
61046
- init_config2();
61532
+ init_config();
61047
61533
 
61048
61534
  // src/tui/utils/open-url.ts
61049
61535
  var OPEN_FAILED_MSG = "Couldn't open browser — visit the URL shown instead";
@@ -61117,7 +61603,7 @@ function AuthFlow({ onClose, hideEsc }) {
61117
61603
  const goHome = () => {
61118
61604
  onClose();
61119
61605
  };
61120
- const cleanup = useCallback14(() => {
61606
+ const cleanup = useCallback16(() => {
61121
61607
  abortRef.current?.abort();
61122
61608
  abortRef.current = null;
61123
61609
  }, []);
@@ -61678,9 +62164,9 @@ function AuthFlow({ onClose, hideEsc }) {
61678
62164
 
61679
62165
  // src/tui/components/commands/credits-flow.tsx
61680
62166
  import { useKeyboard as useKeyboard8 } from "@opentui/react";
61681
- import { useCallback as useCallback15, useEffect as useEffect14, useState as useState22 } from "react";
62167
+ import { useCallback as useCallback17, useEffect as useEffect14, useState as useState22 } from "react";
61682
62168
  init_auth();
61683
- import { jsxDEV as jsxDEV29, Fragment as Fragment2 } from "@opentui/react/jsx-dev-runtime";
62169
+ import { jsxDEV as jsxDEV29, Fragment as Fragment3 } from "@opentui/react/jsx-dev-runtime";
61684
62170
  function CreditsFlow({
61685
62171
  onClose,
61686
62172
  onOpenAuthDialog
@@ -61706,7 +62192,7 @@ function CreditsFlow({
61706
62192
  });
61707
62193
  setStep("browser-opened");
61708
62194
  };
61709
- const fetchBalance = useCallback15(async () => {
62195
+ const fetchBalance = useCallback17(async () => {
61710
62196
  setStep("loading");
61711
62197
  setError(null);
61712
62198
  try {
@@ -61816,7 +62302,7 @@ function CreditsFlow({
61816
62302
  error
61817
62303
  ]
61818
62304
  }, undefined, true, undefined, this)
61819
- }, undefined, false, undefined, this) : credits ? /* @__PURE__ */ jsxDEV29(Fragment2, {
62305
+ }, undefined, false, undefined, this) : credits ? /* @__PURE__ */ jsxDEV29(Fragment3, {
61820
62306
  children: [
61821
62307
  /* @__PURE__ */ jsxDEV29("box", {
61822
62308
  children: /* @__PURE__ */ jsxDEV29("text", {
@@ -62109,7 +62595,6 @@ function HelpDialog({ onClose }) {
62109
62595
  // src/tui/components/commands/models-display.tsx
62110
62596
  import { useKeyboard as useKeyboard10 } from "@opentui/react";
62111
62597
  // src/tui/components/model-picker/ModelPickerDialog.tsx
62112
- init_ai();
62113
62598
  import { jsxDEV as jsxDEV31 } from "@opentui/react/jsx-dev-runtime";
62114
62599
  function ModelPickerDialog({ onClose }) {
62115
62600
  const { colors: colors2 } = useTheme();
@@ -62123,17 +62608,9 @@ function ModelPickerDialog({ onClose }) {
62123
62608
  openAIReasoningEffort,
62124
62609
  setOpenAIReasoningEffort
62125
62610
  } = useAgent();
62126
- const thinkingSupported = modelSupportsThinking(model.id);
62127
- const openAIReasoningSupported = modelSupportsOpenAIReasoning(model.id);
62128
62611
  const footerActions = [
62129
62612
  { key: "Enter", label: "confirm", variant: "primary" }
62130
62613
  ];
62131
- if (thinkingSupported) {
62132
- footerActions.push({ key: "Space", label: "Extended Thinking" });
62133
- }
62134
- if (openAIReasoningSupported) {
62135
- footerActions.push({ key: "Space", label: "Reasoning Effort" });
62136
- }
62137
62614
  const title = /* @__PURE__ */ jsxDEV31("text", {
62138
62615
  children: [
62139
62616
  /* @__PURE__ */ jsxDEV31("span", {
@@ -62609,7 +63086,7 @@ function HITLWizard(props) {
62609
63086
  }
62610
63087
 
62611
63088
  // src/tui/components/commands/provider-manager.tsx
62612
- init_config2();
63089
+ init_config();
62613
63090
  import { useKeyboard as useKeyboard14 } from "@opentui/react";
62614
63091
  import { useState as useState27 } from "react";
62615
63092
 
@@ -62863,7 +63340,7 @@ function ProviderSelection({
62863
63340
  }
62864
63341
 
62865
63342
  // src/tui/components/commands/provider-manager.tsx
62866
- import { jsxDEV as jsxDEV37, Fragment as Fragment3 } from "@opentui/react/jsx-dev-runtime";
63343
+ import { jsxDEV as jsxDEV37, Fragment as Fragment4 } from "@opentui/react/jsx-dev-runtime";
62867
63344
  function ProviderManager({
62868
63345
  onClose,
62869
63346
  onOpenModelDialog
@@ -62939,7 +63416,7 @@ function ProviderManager({
62939
63416
  };
62940
63417
  const otherProviders = AVAILABLE_PROVIDERS.filter((p) => p.id !== "pensar");
62941
63418
  const selectedProviderInfo = AVAILABLE_PROVIDERS.find((p) => p.id === selectedProvider);
62942
- return /* @__PURE__ */ jsxDEV37(Fragment3, {
63419
+ return /* @__PURE__ */ jsxDEV37(Fragment4, {
62943
63420
  children: [
62944
63421
  flowState === "choosing" && /* @__PURE__ */ jsxDEV37(OnboardingChoice, {
62945
63422
  onPensarSelected: () => setFlowState("auth"),
@@ -63052,13 +63529,13 @@ function OnboardingChoice({
63052
63529
 
63053
63530
  // src/tui/components/commands/sessions-display.tsx
63054
63531
  import { useKeyboard as useKeyboard16 } from "@opentui/react";
63055
- import { useCallback as useCallback17, useEffect as useEffect18, useRef as useRef14, useState as useState29 } from "react";
63532
+ import { useCallback as useCallback19, useEffect as useEffect18, useRef as useRef14, useState as useState29 } from "react";
63056
63533
  init_session();
63057
63534
 
63058
63535
  // src/tui/hooks/use-sessions-list.ts
63059
63536
  import { existsSync, readdirSync } from "node:fs";
63060
63537
  import { join } from "node:path";
63061
- import { useCallback as useCallback16, useEffect as useEffect17, useState as useState28 } from "react";
63538
+ import { useCallback as useCallback18, useEffect as useEffect17, useState as useState28 } from "react";
63062
63539
  init_session();
63063
63540
  function countFindings(findingsPath) {
63064
63541
  try {
@@ -63076,7 +63553,7 @@ function useSessionsList() {
63076
63553
  const [allSessions, setAllSessions] = useState28([]);
63077
63554
  const [loading, setLoading] = useState28(true);
63078
63555
  const [searchTerm, setSearchTerm] = useState28("");
63079
- const loadSessions = useCallback16(async () => {
63556
+ const loadSessions = useCallback18(async () => {
63080
63557
  try {
63081
63558
  const enriched = [];
63082
63559
  for await (const session of list()) {
@@ -63101,7 +63578,7 @@ function useSessionsList() {
63101
63578
  useEffect17(() => {
63102
63579
  loadSessions();
63103
63580
  }, [loadSessions]);
63104
- const deleteSession = useCallback16(async (id) => {
63581
+ const deleteSession = useCallback18(async (id) => {
63105
63582
  setAllSessions((prev) => prev.filter((s) => s.id !== id));
63106
63583
  await sessions.remove({ sessionId: id });
63107
63584
  await loadSessions();
@@ -63334,7 +63811,7 @@ function SessionsDisplay({ onClose }) {
63334
63811
  initialOverflowRef.current = estimatedRows > availableListHeight;
63335
63812
  }
63336
63813
  const listHeight = initialOverflowRef.current ? availableListHeight : undefined;
63337
- const viewReport = useCallback17(async (sessionId) => {
63814
+ const viewReport = useCallback19(async (sessionId) => {
63338
63815
  const session = await sessions.get(sessionId);
63339
63816
  const content = readSessionReport(session.rootPath);
63340
63817
  if (!content) {
@@ -63345,7 +63822,7 @@ function SessionsDisplay({ onClose }) {
63345
63822
  setReportSessionPath(session.rootPath);
63346
63823
  setShowReportViewer(true);
63347
63824
  }, [toast]);
63348
- const openReportExternal = useCallback17(async () => {
63825
+ const openReportExternal = useCallback19(async () => {
63349
63826
  if (!reportSessionPath)
63350
63827
  return;
63351
63828
  const err = await openSessionReport(reportSessionPath);
@@ -63971,9 +64448,9 @@ function SkillsDialog({
63971
64448
  }
63972
64449
 
63973
64450
  // src/tui/components/commands/theme-picker.tsx
63974
- init_config2();
64451
+ init_config();
63975
64452
  import { useKeyboard as useKeyboard19 } from "@opentui/react";
63976
- import { useCallback as useCallback18, useRef as useRef16, useState as useState31 } from "react";
64453
+ import { useCallback as useCallback20, useRef as useRef16, useState as useState31 } from "react";
63977
64454
  import { jsxDEV as jsxDEV42 } from "@opentui/react/jsx-dev-runtime";
63978
64455
  function ThemePicker({ onClose }) {
63979
64456
  const dimensions = useDimensions();
@@ -63992,14 +64469,14 @@ function ThemePicker({ onClose }) {
63992
64469
  const originalThemeRef = useRef16(theme.name);
63993
64470
  const originalModeRef = useRef16(mode);
63994
64471
  const originalTransparentRef = useRef16(transparent);
63995
- const handleClose = useCallback18(() => {
64472
+ const handleClose = useCallback20(() => {
63996
64473
  setTheme(originalThemeRef.current);
63997
64474
  setMode(originalModeRef.current);
63998
64475
  if (transparent !== originalTransparentRef.current)
63999
64476
  toggleTransparent();
64000
64477
  onClose();
64001
64478
  }, [setTheme, setMode, transparent, toggleTransparent, onClose]);
64002
- const handleConfirm = useCallback18(async () => {
64479
+ const handleConfirm = useCallback20(async () => {
64003
64480
  const currentThemeName = availableThemes[selectedIndex];
64004
64481
  if (currentThemeName) {
64005
64482
  await config.update({ theme: currentThemeName });
@@ -64125,7 +64602,7 @@ function ThemePicker({ onClose }) {
64125
64602
  // src/tui/components/commands/web-wizard.tsx
64126
64603
  import { useKeyboard as useKeyboard20 } from "@opentui/react";
64127
64604
  import { useEffect as useEffect20, useRef as useRef17, useState as useState32 } from "react";
64128
- import { jsxDEV as jsxDEV43, Fragment as Fragment4 } from "@opentui/react/jsx-dev-runtime";
64605
+ import { jsxDEV as jsxDEV43, Fragment as Fragment5 } from "@opentui/react/jsx-dev-runtime";
64129
64606
  function capitalize(s) {
64130
64607
  return s.charAt(0).toUpperCase() + s.slice(1);
64131
64608
  }
@@ -64611,7 +65088,7 @@ function WebWizard({
64611
65088
  }, undefined, false, undefined, this)
64612
65089
  ]
64613
65090
  }, undefined, true, undefined, this),
64614
- advancedExpanded && /* @__PURE__ */ jsxDEV43(Fragment4, {
65091
+ advancedExpanded && /* @__PURE__ */ jsxDEV43(Fragment5, {
64615
65092
  children: [
64616
65093
  /* @__PURE__ */ jsxDEV43("box", {
64617
65094
  id: `field-${promptFieldIndex}`,
@@ -64849,7 +65326,7 @@ function WebWizard({
64849
65326
  }, undefined, false, undefined, this)
64850
65327
  ]
64851
65328
  }, undefined, true, undefined, this),
64852
- state.headers.mode === "custom" && /* @__PURE__ */ jsxDEV43(Fragment4, {
65329
+ state.headers.mode === "custom" && /* @__PURE__ */ jsxDEV43(Fragment5, {
64853
65330
  children: [
64854
65331
  /* @__PURE__ */ jsxDEV43("box", {
64855
65332
  id: `field-${headersNameIndex}`,
@@ -64897,7 +65374,7 @@ function WebWizard({
64897
65374
 
64898
65375
  // src/tui/components/error-boundary.tsx
64899
65376
  init_logger();
64900
- import React, { useCallback as useCallback19 } from "react";
65377
+ import React, { useCallback as useCallback21 } from "react";
64901
65378
  var MAX_ERRORS = 3;
64902
65379
  var ERROR_WINDOW_MS = 5000;
64903
65380
 
@@ -64932,7 +65409,7 @@ class ErrorBoundaryInner extends React.Component {
64932
65409
  }
64933
65410
  function ErrorBoundary({ children }) {
64934
65411
  const { toast } = useToast();
64935
- const handleError = useCallback19((message) => {
65412
+ const handleError = useCallback21((message) => {
64936
65413
  toast(message, "error");
64937
65414
  }, [toast]);
64938
65415
  return React.createElement(ErrorBoundaryInner, { onError: handleError }, children);
@@ -64986,19 +65463,52 @@ var useSession = () => {
64986
65463
  return session;
64987
65464
  };
64988
65465
 
64989
- // src/tui/components/footer.tsx
64990
- import { jsxDEV as jsxDEV45, Fragment as Fragment5 } from "@opentui/react/jsx-dev-runtime";
65466
+ // src/tui/components/usage-labels.ts
65467
+ var WIDE_MIN_WIDTH = 86;
65468
+ var NARROW_MIN_WIDTH = 56;
64991
65469
  function formatTokenCount(count) {
64992
65470
  if (count >= 1e6) {
64993
65471
  const v2 = count / 1e6;
64994
- return `${Number.isInteger(v2) ? v2.toFixed(0) : v2.toFixed(1)}M`;
65472
+ return `${Number.isInteger(v2) ? v2.toFixed(0) : v2.toFixed(1)}m`;
64995
65473
  }
64996
65474
  if (count >= 1000) {
64997
65475
  const v2 = count / 1000;
64998
- return `${Number.isInteger(v2) ? v2.toFixed(0) : v2.toFixed(1)}K`;
65476
+ return `${Number.isInteger(v2) ? v2.toFixed(0) : v2.toFixed(1)}k`;
64999
65477
  }
65000
65478
  return count.toString();
65001
65479
  }
65480
+ function buildUsageFooterLabels(input) {
65481
+ const { tokenUsage, contextUsage, width } = input;
65482
+ if (width < NARROW_MIN_WIDTH) {
65483
+ return { tokensLabel: null, contextLabel: null };
65484
+ }
65485
+ if (width >= WIDE_MIN_WIDTH) {
65486
+ let tokensLabel = `in ${formatTokenCount(tokenUsage.inputTokens)} out ${formatTokenCount(tokenUsage.outputTokens)}`;
65487
+ if (tokenUsage.cacheReadTokens > 0) {
65488
+ tokensLabel += ` cached ${formatTokenCount(tokenUsage.cacheReadTokens)}`;
65489
+ }
65490
+ return {
65491
+ tokensLabel,
65492
+ contextLabel: contextLabel(contextUsage, true)
65493
+ };
65494
+ }
65495
+ const total = tokenUsage.inputTokens + tokenUsage.outputTokens;
65496
+ return {
65497
+ tokensLabel: `${formatTokenCount(total)} tokens`,
65498
+ contextLabel: contextLabel(contextUsage, false)
65499
+ };
65500
+ }
65501
+ function contextLabel(context2, wide) {
65502
+ const percentage = contextPercentage(context2);
65503
+ if (percentage === null || !context2)
65504
+ return null;
65505
+ const pct = Math.floor(percentage);
65506
+ const limit = formatTokenCount(context2.contextLimit);
65507
+ return wide ? `context ${pct}% / ${limit}` : `${pct}% / ${limit}`;
65508
+ }
65509
+
65510
+ // src/tui/components/footer.tsx
65511
+ import { jsxDEV as jsxDEV45, Fragment as Fragment6 } from "@opentui/react/jsx-dev-runtime";
65002
65512
  function Footer({
65003
65513
  cwd = process.cwd(),
65004
65514
  showExitWarning = false
@@ -65077,126 +65587,54 @@ function Footer({
65077
65587
  }
65078
65588
  function AgentStatus() {
65079
65589
  const { colors: colors2 } = useTheme();
65080
- const { tokenUsage } = useAgent();
65590
+ const { usageStore } = useAgent();
65081
65591
  const { width: termWidth } = useDimensions();
65082
- const showTokenCount = termWidth > 85;
65083
- const tokenLabel = `↓${formatTokenCount(tokenUsage.inputTokens)} ↑${formatTokenCount(tokenUsage.outputTokens)}${tokenUsage.cachedTokens > 0 ? ` ⚡${formatTokenCount(tokenUsage.cachedTokens)}` : ""} Σ${formatTokenCount(tokenUsage.totalTokens)}`;
65592
+ const usage = useActiveSessionUsage(usageStore);
65593
+ const { tokensLabel, contextLabel: contextLabel2 } = buildUsageFooterLabels({
65594
+ tokenUsage: usage.tokenUsage,
65595
+ contextUsage: usage.contextUsage,
65596
+ width: termWidth
65597
+ });
65598
+ if (!tokensLabel)
65599
+ return null;
65084
65600
  return /* @__PURE__ */ jsxDEV45("box", {
65085
65601
  flexDirection: "row",
65086
65602
  gap: 1,
65087
65603
  children: [
65088
- showTokenCount && /* @__PURE__ */ jsxDEV45(Fragment5, {
65604
+ /* @__PURE__ */ jsxDEV45("text", {
65605
+ fg: colors2.text,
65606
+ children: tokensLabel
65607
+ }, undefined, false, undefined, this),
65608
+ contextLabel2 && /* @__PURE__ */ jsxDEV45(Fragment6, {
65089
65609
  children: [
65090
- /* @__PURE__ */ jsxDEV45("box", {
65091
- border: ["right"],
65092
- borderColor: colors2.primary
65610
+ /* @__PURE__ */ jsxDEV45("text", {
65611
+ fg: colors2.textMuted,
65612
+ children: "|"
65093
65613
  }, undefined, false, undefined, this),
65094
65614
  /* @__PURE__ */ jsxDEV45("text", {
65095
65615
  fg: colors2.text,
65096
- children: tokenLabel
65097
- }, undefined, false, undefined, this),
65098
- /* @__PURE__ */ jsxDEV45("box", {
65099
- border: ["right"],
65100
- borderColor: colors2.primary
65616
+ children: contextLabel2
65101
65617
  }, undefined, false, undefined, this)
65102
65618
  ]
65103
- }, undefined, true, undefined, this),
65104
- /* @__PURE__ */ jsxDEV45(ContextProgress, {
65105
- width: 16,
65106
- showPercent: true
65107
- }, undefined, false, undefined, this)
65619
+ }, undefined, true, undefined, this)
65108
65620
  ]
65109
65621
  }, undefined, true, undefined, this);
65110
65622
  }
65111
- var FILL_CHAR = "▬";
65112
- var DASH_CHAR = "─";
65113
- function ContextProgress({
65114
- width = 20,
65115
- showPercent = false
65116
- }) {
65117
- const { colors: colors2 } = useTheme();
65118
- const { model, tokenUsage } = useAgent();
65119
- const contextLength = model.contextLength ?? 200000;
65120
- const fraction = Math.max(0, Math.min(1, tokenUsage.totalTokens / contextLength));
65121
- const inner = Math.max(1, width - 2);
65122
- const filledCells = Math.round(fraction * inner);
65123
- const fillColor = colors2.success;
65124
- const sizeText = formatTokenCount(contextLength);
65125
- const labelText = showPercent ? `${Math.floor(fraction * 100)}% ${sizeText}` : sizeText;
65126
- const labelLen = labelText.length;
65127
- const labelStart = Math.max(0, inner - labelLen - 1);
65128
- const labelEnd = labelStart + labelLen;
65129
- const dashColor = withAlpha2(colors2.textMuted, 0.35);
65130
- const bracketColor = withAlpha2(colors2.textMuted, 0.7);
65131
- const cells = [];
65132
- cells.push({ ch: "[", fg: bracketColor });
65133
- for (let col = 0;col < inner; col++) {
65134
- const inLabel = col >= labelStart && col < labelEnd;
65135
- const isFilled = col < filledCells;
65136
- if (inLabel) {
65137
- cells.push({
65138
- ch: labelText[col - labelStart],
65139
- fg: colors2.text
65140
- });
65141
- } else if (isFilled) {
65142
- cells.push({ ch: FILL_CHAR, fg: fillColor });
65143
- } else {
65144
- cells.push({ ch: DASH_CHAR, fg: dashColor });
65145
- }
65146
- }
65147
- cells.push({ ch: "]", fg: bracketColor });
65148
- return /* @__PURE__ */ jsxDEV45("box", {
65149
- flexDirection: "row",
65150
- children: cells.map((cell, i) => /* @__PURE__ */ jsxDEV45("text", {
65151
- fg: cell.fg,
65152
- content: cell.ch
65153
- }, i, false, undefined, this))
65154
- }, undefined, false, undefined, this);
65155
- }
65156
65623
 
65157
65624
  // src/tui/components/operator-dashboard/index.tsx
65158
65625
  init_dist();
65159
- import { existsSync as existsSync3, readFileSync as readFileSync3, writeFileSync } from "node:fs";
65160
- import { isAbsolute as isAbsolute2, join as join3, resolve as resolve2 } from "node:path";
65626
+ import { existsSync as existsSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync2 } from "node:fs";
65627
+ import { isAbsolute as isAbsolute2, join as join4, resolve as resolve2 } from "node:path";
65161
65628
  import { useKeyboard as useKeyboard26 } from "@opentui/react";
65162
65629
  import {
65163
- useCallback as useCallback20,
65630
+ useCallback as useCallback22,
65164
65631
  useEffect as useEffect25,
65165
65632
  useMemo as useMemo22,
65166
65633
  useRef as useRef21,
65167
65634
  useState as useState39,
65168
- useSyncExternalStore as useSyncExternalStore2
65635
+ useSyncExternalStore as useSyncExternalStore3
65169
65636
  } from "react";
65170
65637
  init_ai();
65171
-
65172
- // src/core/integrations/wandb/upload.ts
65173
- async function createWandbUploader(session, opts) {
65174
- const resolved = resolveConfig(opts);
65175
- if (!resolved.available)
65176
- return null;
65177
- const tracer = await createWeaveTracer(resolved.config);
65178
- if (!tracer)
65179
- return null;
65180
- return {
65181
- onRecord: (record) => tracer.logRecord(record, session.id),
65182
- finalize: () => tracer.finish()
65183
- };
65184
- }
65185
- async function attachWandbToEventBus(session, eventBus, opts) {
65186
- const uploader = await createWandbUploader(session, opts);
65187
- if (!uploader)
65188
- return null;
65189
- const handler = (e) => {
65190
- uploader.onRecord(e.record);
65191
- };
65192
- eventBus.on("trace-record", handler);
65193
- return async () => {
65194
- eventBus.off("trace-record", handler);
65195
- await uploader.finalize();
65196
- };
65197
- }
65198
-
65199
- // src/tui/components/operator-dashboard/index.tsx
65200
65638
  init_session();
65201
65639
 
65202
65640
  // src/tui/components/chat/approval-inline.tsx
@@ -66464,6 +66902,202 @@ function ScreenshotModal({ screenshots, initialIndex, onClose }) {
66464
66902
  }, undefined, true, undefined, this);
66465
66903
  }
66466
66904
 
66905
+ // src/tui/components/operator-dashboard/conversation.ts
66906
+ init_session();
66907
+ import { existsSync as existsSync3, readFileSync as readFileSync3, writeFileSync } from "node:fs";
66908
+ import { join as join3 } from "node:path";
66909
+ function recoverAbortedConversation(conversation, partialText, displayMessages) {
66910
+ if (conversation.at(-1)?.role !== "user")
66911
+ return null;
66912
+ const pendingTools = displayMessages.filter((message) => message.role === "tool" && typeof message.toolCallId === "string" && typeof message.toolName === "string" && (message.status === "pending" || message.status === "streaming"));
66913
+ const assistantContent = [
66914
+ {
66915
+ type: "text",
66916
+ text: partialText.trim() || "[Response interrupted by user.]"
66917
+ },
66918
+ ...pendingTools.map((tool) => ({
66919
+ type: "tool-call",
66920
+ toolCallId: tool.toolCallId,
66921
+ toolName: tool.toolName,
66922
+ input: tool.args ?? {}
66923
+ }))
66924
+ ];
66925
+ const recovered = [
66926
+ ...conversation,
66927
+ {
66928
+ role: "assistant",
66929
+ content: assistantContent
66930
+ }
66931
+ ];
66932
+ if (pendingTools.length === 0)
66933
+ return recovered;
66934
+ return [
66935
+ ...recovered,
66936
+ {
66937
+ role: "tool",
66938
+ content: pendingTools.map((tool) => ({
66939
+ type: "tool-result",
66940
+ toolCallId: tool.toolCallId,
66941
+ toolName: tool.toolName ?? "unknown",
66942
+ output: {
66943
+ type: "text",
66944
+ value: "Cancelled by user."
66945
+ }
66946
+ }))
66947
+ }
66948
+ ];
66949
+ }
66950
+ function rewriteToolResultOutput(conversation, toolCallId, value) {
66951
+ const wrappedOutput = {
66952
+ type: "json",
66953
+ value
66954
+ };
66955
+ let changed = false;
66956
+ const next = conversation.map((msg) => {
66957
+ if (msg.role !== "tool")
66958
+ return msg;
66959
+ if (!Array.isArray(msg.content))
66960
+ return msg;
66961
+ let mutated = false;
66962
+ const nextContent = msg.content.map((part) => {
66963
+ if (part && typeof part === "object" && "type" in part && part.type === "tool-result" && part.toolCallId === toolCallId) {
66964
+ mutated = true;
66965
+ return {
66966
+ ...part,
66967
+ output: wrappedOutput
66968
+ };
66969
+ }
66970
+ return part;
66971
+ });
66972
+ if (!mutated)
66973
+ return msg;
66974
+ changed = true;
66975
+ return { ...msg, content: nextContent };
66976
+ });
66977
+ return changed ? next : conversation;
66978
+ }
66979
+ function recoverAbortedTranscript(input) {
66980
+ const messagesPath = join3(input.rootPath, "messages.json");
66981
+ let conversation = input.conversation;
66982
+ let recovered = null;
66983
+ try {
66984
+ if (existsSync3(messagesPath)) {
66985
+ const raw = JSON.parse(readFileSync3(messagesPath, "utf-8"));
66986
+ if (Array.isArray(raw) && raw.length > 0) {
66987
+ conversation = normalizeMessages(getResumeMessages(raw));
66988
+ }
66989
+ }
66990
+ recovered = recoverAbortedConversation(conversation, input.partialText, input.displayMessages);
66991
+ } catch {}
66992
+ if (!recovered)
66993
+ return conversation;
66994
+ conversation = recovered;
66995
+ try {
66996
+ writeFileSync(messagesPath, JSON.stringify(recovered, null, 2));
66997
+ } catch {}
66998
+ return conversation;
66999
+ }
67000
+
67001
+ // src/tui/components/operator-dashboard/display-state.ts
67002
+ var MAX_LOG_LINES = 200;
67003
+ function appendStreamedText(messages, accumulated) {
67004
+ const last = messages[messages.length - 1];
67005
+ if (last && last.role === "assistant") {
67006
+ const updated = [...messages];
67007
+ updated[updated.length - 1] = { ...last, content: accumulated };
67008
+ return updated;
67009
+ }
67010
+ return [
67011
+ ...messages,
67012
+ { role: "assistant", content: accumulated, createdAt: new Date }
67013
+ ];
67014
+ }
67015
+ function startStreamingToolCall(messages, toolCallId, toolName) {
67016
+ return [
67017
+ ...messages,
67018
+ {
67019
+ role: "tool",
67020
+ content: "",
67021
+ createdAt: new Date,
67022
+ toolCallId,
67023
+ toolName,
67024
+ args: {},
67025
+ status: "streaming"
67026
+ }
67027
+ ];
67028
+ }
67029
+ function applyToolCallDelta(messages, toolCallId, parsed) {
67030
+ const contentText = extractStreamableContent(parsed);
67031
+ const logs = contentText ? contentText.split(`
67032
+ `) : undefined;
67033
+ const idx = messages.findIndex((m2) => isToolMessage(m2) && m2.toolCallId === toolCallId);
67034
+ if (idx === -1)
67035
+ return messages;
67036
+ const updated = [...messages];
67037
+ updated[idx] = { ...updated[idx], args: parsed, ...logs && { logs } };
67038
+ return updated;
67039
+ }
67040
+ function applyToolCall(messages, toolCallId, toolName, args) {
67041
+ const idx = messages.findIndex((m2) => isToolMessage(m2) && m2.toolCallId === toolCallId);
67042
+ if (idx !== -1) {
67043
+ const updated = [...messages];
67044
+ updated[idx] = {
67045
+ ...updated[idx],
67046
+ args,
67047
+ logs: undefined,
67048
+ status: "pending"
67049
+ };
67050
+ return updated;
67051
+ }
67052
+ return [
67053
+ ...messages,
67054
+ {
67055
+ role: "tool",
67056
+ content: "",
67057
+ createdAt: new Date,
67058
+ toolCallId,
67059
+ toolName,
67060
+ args,
67061
+ status: "pending"
67062
+ }
67063
+ ];
67064
+ }
67065
+ function applyToolResult(messages, toolCallId, result) {
67066
+ const idx = messages.findIndex((m2) => isToolMessage(m2) && m2.toolCallId === toolCallId);
67067
+ if (idx === -1)
67068
+ return messages;
67069
+ const updated = [...messages];
67070
+ updated[idx] = { ...updated[idx], status: "completed", result };
67071
+ return updated;
67072
+ }
67073
+ function mergeCommandOutput(messages, buf) {
67074
+ const idx = messages.findLastIndex((m2) => isToolMessage(m2) && (m2.status === "pending" || m2.status === "streaming"));
67075
+ if (idx === -1)
67076
+ return messages;
67077
+ const msg = messages[idx];
67078
+ const existing = msg.logs ?? [];
67079
+ const incoming = buf.split(`
67080
+ `);
67081
+ let merged;
67082
+ if (existing.length > 0 && !buf.startsWith(`
67083
+ `)) {
67084
+ merged = [...existing];
67085
+ merged[merged.length - 1] += incoming[0];
67086
+ merged.push(...incoming.slice(1));
67087
+ } else {
67088
+ merged = [...existing, ...incoming];
67089
+ }
67090
+ if (merged.length > MAX_LOG_LINES) {
67091
+ merged = merged.slice(-MAX_LOG_LINES);
67092
+ }
67093
+ const updated = [...messages];
67094
+ updated[idx] = { ...msg, logs: merged };
67095
+ return updated;
67096
+ }
67097
+ function markInFlightToolsErrored(messages, result) {
67098
+ return messages.map((m2) => isToolMessage(m2) && (m2.status === "pending" || m2.status === "streaming") ? { ...m2, status: "error", result } : m2);
67099
+ }
67100
+
66467
67101
  // src/tui/components/operator-dashboard/logic.ts
66468
67102
  var OPERATOR_ALLOWED_COMMANDS = new Set([
66469
67103
  "/models",
@@ -66741,17 +67375,6 @@ ${refinementBlock}`;
66741
67375
  function resolveInputFocused(status, dialogStackLength, externalDialogOpen) {
66742
67376
  return status !== "running" && dialogStackLength === 0 && !externalDialogOpen;
66743
67377
  }
66744
- function accumulateTokenUsage(current, stepInputTokens, stepOutputTokens) {
66745
- if (stepInputTokens <= 0 && stepOutputTokens <= 0)
66746
- return null;
66747
- return {
66748
- inputTokens: current.inputTokens + stepInputTokens,
66749
- outputTokens: current.outputTokens + stepOutputTokens,
66750
- totalTokens: current.totalTokens + stepInputTokens + stepOutputTokens,
66751
- cachedTokens: current.cachedTokens,
66752
- cacheWriteTokens: current.cacheWriteTokens
66753
- };
66754
- }
66755
67378
 
66756
67379
  // src/tui/components/operator-dashboard/queue.ts
66757
67380
  var nextId3 = 0;
@@ -66841,6 +67464,401 @@ function QueuedMessages({
66841
67464
  }, undefined, true, undefined, this);
66842
67465
  }
66843
67466
 
67467
+ // src/tui/components/operator-dashboard/workflow-data.ts
67468
+ function isPentestAgent(id) {
67469
+ return id !== undefined && /^pentest-agent-\d+$/.test(id);
67470
+ }
67471
+ function initialWorkflowData() {
67472
+ return {
67473
+ currentPhase: "discovery",
67474
+ discovery: { label: "", status: "pending", logs: [] },
67475
+ pentesting: { label: "", status: "idle", subagents: {} },
67476
+ reporting: { label: "", status: "idle" }
67477
+ };
67478
+ }
67479
+ function updateWorkflowDataMessage(messages, updater) {
67480
+ const idx = messages.findLastIndex((m2) => isToolMessage(m2) && m2.toolName === "run_pentest_workflow" && (m2.status === "pending" || m2.status === "streaming"));
67481
+ if (idx === -1)
67482
+ return messages;
67483
+ const msg = messages[idx];
67484
+ const wd = msg.workflowData ?? initialWorkflowData();
67485
+ const updated = [...messages];
67486
+ updated[idx] = { ...msg, workflowData: updater(wd) };
67487
+ return updated;
67488
+ }
67489
+ function applyWorkflowSubagentSpawn(wd, subagentId, name) {
67490
+ return {
67491
+ ...wd,
67492
+ pentesting: {
67493
+ ...wd.pentesting,
67494
+ subagents: {
67495
+ ...wd.pentesting.subagents,
67496
+ [subagentId]: { name, status: "pending", logs: [] }
67497
+ }
67498
+ }
67499
+ };
67500
+ }
67501
+ function applyWorkflowSubagentComplete(wd, subagentId, status) {
67502
+ const entry = wd.pentesting.subagents[subagentId];
67503
+ if (!entry)
67504
+ return wd;
67505
+ return {
67506
+ ...wd,
67507
+ pentesting: {
67508
+ ...wd.pentesting,
67509
+ subagents: {
67510
+ ...wd.pentesting.subagents,
67511
+ [subagentId]: { ...entry, status }
67512
+ }
67513
+ }
67514
+ };
67515
+ }
67516
+ function applyWorkflowPhaseStart(wd, phase, label) {
67517
+ const next = { ...wd, currentPhase: phase };
67518
+ if (phase === "discovery") {
67519
+ next.discovery = { ...wd.discovery, label, status: "pending", logs: [] };
67520
+ } else if (phase === "pentesting") {
67521
+ next.pentesting = { ...wd.pentesting, label, status: "pending" };
67522
+ } else if (phase === "reporting") {
67523
+ next.reporting = { ...wd.reporting, label, status: "pending" };
67524
+ }
67525
+ return next;
67526
+ }
67527
+ function applyWorkflowPhaseComplete(wd, phase, summary) {
67528
+ const next = { ...wd };
67529
+ if (phase === "discovery") {
67530
+ const targets = summary.targets ?? [];
67531
+ next.discovery = {
67532
+ ...wd.discovery,
67533
+ status: "complete",
67534
+ targets,
67535
+ cached: summary.cached
67536
+ };
67537
+ } else if (phase === "pentesting") {
67538
+ next.pentesting = { ...wd.pentesting, status: "complete" };
67539
+ } else if (phase === "reporting") {
67540
+ next.reporting = {
67541
+ ...wd.reporting,
67542
+ status: "complete",
67543
+ findingsCount: summary.findingsCount,
67544
+ findingsBySeverity: summary.findingsBySeverity,
67545
+ reportPath: summary.reportPath
67546
+ };
67547
+ next.currentPhase = "complete";
67548
+ }
67549
+ return next;
67550
+ }
67551
+
67552
+ // src/tui/components/operator-dashboard/run-events.ts
67553
+ function bindOperatorRunEvents(bus, options) {
67554
+ const { isCurrent, handlers } = options;
67555
+ const offs = [];
67556
+ let boundError = false;
67557
+ const bind = (event, handler) => {
67558
+ if (!handler)
67559
+ return;
67560
+ if (event === "error")
67561
+ boundError = true;
67562
+ const wrapped = (e) => {
67563
+ if (!isCurrent())
67564
+ return;
67565
+ try {
67566
+ handler(e);
67567
+ } catch (err) {
67568
+ console.error(`[operator] ${event} handler failed:`, err);
67569
+ }
67570
+ };
67571
+ bus.on(event, wrapped);
67572
+ offs.push(() => bus.off(event, wrapped));
67573
+ };
67574
+ bind("text-delta", handlers.onTextDelta);
67575
+ bind("tool-call-start", handlers.onToolCallStart);
67576
+ bind("tool-call-delta", handlers.onToolCallDelta);
67577
+ bind("tool-call-complete", handlers.onToolCallComplete);
67578
+ bind("tool-result", handlers.onToolResult);
67579
+ bind("command-output", handlers.onCommandOutput);
67580
+ bind("error", handlers.onError);
67581
+ bind("subagent-spawn", handlers.onSubagentSpawn);
67582
+ bind("subagent-complete", handlers.onSubagentComplete);
67583
+ bind("workflow-phase-start", handlers.onWorkflowPhaseStart);
67584
+ bind("workflow-phase-complete", handlers.onWorkflowPhaseComplete);
67585
+ let unbound = false;
67586
+ return () => {
67587
+ if (unbound)
67588
+ return;
67589
+ unbound = true;
67590
+ for (const off of offs)
67591
+ off();
67592
+ offs.length = 0;
67593
+ if (boundError)
67594
+ bus.on("error", () => {});
67595
+ };
67596
+ }
67597
+ var COMMAND_OUTPUT_FLUSH_MS = 150;
67598
+ function createDisplayEventHandlers(sink) {
67599
+ let partialText = "";
67600
+ const toolArgsDeltas = new Map;
67601
+ let commandOutputBuf = "";
67602
+ let flushTimer = null;
67603
+ const flushCommandOutput = () => {
67604
+ if (!commandOutputBuf)
67605
+ return;
67606
+ const buf = commandOutputBuf;
67607
+ commandOutputBuf = "";
67608
+ sink.updateMessages((messages) => mergeCommandOutput(messages, buf));
67609
+ };
67610
+ const stopCommandOutputFlush = () => {
67611
+ if (flushTimer) {
67612
+ clearInterval(flushTimer);
67613
+ flushTimer = null;
67614
+ }
67615
+ };
67616
+ return {
67617
+ onTextDelta(e) {
67618
+ sink.setThinking(false);
67619
+ partialText += e.text;
67620
+ const accumulated = partialText;
67621
+ sink.updateMessages((messages) => appendStreamedText(messages, accumulated));
67622
+ },
67623
+ onToolCallStart(e) {
67624
+ sink.setThinking(false);
67625
+ partialText = "";
67626
+ toolArgsDeltas.set(e.toolCallId, { accumulated: "" });
67627
+ sink.updateMessages((messages) => startStreamingToolCall(messages, e.toolCallId, e.toolName));
67628
+ },
67629
+ onToolCallDelta(e) {
67630
+ const entry = toolArgsDeltas.get(e.toolCallId);
67631
+ const accumulated = (entry?.accumulated ?? "") + e.argsTextDelta;
67632
+ toolArgsDeltas.set(e.toolCallId, { accumulated });
67633
+ const parsed = tryParsePartialJson(accumulated);
67634
+ if (!parsed)
67635
+ return;
67636
+ sink.updateMessages((messages) => applyToolCallDelta(messages, e.toolCallId, parsed));
67637
+ },
67638
+ onToolCallComplete(e) {
67639
+ sink.setThinking(false);
67640
+ partialText = "";
67641
+ toolArgsDeltas.delete(e.toolCallId);
67642
+ const args = e.args && typeof e.args === "object" && !Array.isArray(e.args) && e.args !== null ? e.args : undefined;
67643
+ sink.updateMessages((messages) => applyToolCall(messages, e.toolCallId, e.toolName, args));
67644
+ },
67645
+ onToolResult(e) {
67646
+ flushCommandOutput();
67647
+ stopCommandOutputFlush();
67648
+ sink.setThinking(true);
67649
+ partialText = "";
67650
+ sink.updateMessages((messages) => applyToolResult(messages, e.toolCallId, e.result));
67651
+ },
67652
+ onCommandOutput(e) {
67653
+ commandOutputBuf += e.data;
67654
+ if (!flushTimer) {
67655
+ flushTimer = setInterval(flushCommandOutput, COMMAND_OUTPUT_FLUSH_MS);
67656
+ }
67657
+ },
67658
+ onError(e) {
67659
+ console.error("Agent error:", e.error);
67660
+ const errorMessage = e.error instanceof Error ? e.error.message : "Unknown error";
67661
+ sink.setError(errorMessage);
67662
+ sink.updateMessages((messages) => markInFlightToolsErrored(messages, errorMessage));
67663
+ },
67664
+ getPartialText() {
67665
+ return partialText;
67666
+ },
67667
+ resetPartialText() {
67668
+ partialText = "";
67669
+ },
67670
+ flushCommandOutput,
67671
+ stopCommandOutputFlush,
67672
+ dispose: stopCommandOutputFlush
67673
+ };
67674
+ }
67675
+ function toRecordArgs(args) {
67676
+ return args && typeof args === "object" && !Array.isArray(args) && args !== null ? args : undefined;
67677
+ }
67678
+ function createRunEventProjections(deps) {
67679
+ const { display, subagents, updateWorkflowData } = deps;
67680
+ return {
67681
+ handlers: {
67682
+ onTextDelta: (d2) => {
67683
+ if (d2.subagentId) {
67684
+ subagents.appendText(d2.subagentId, d2.text);
67685
+ return;
67686
+ }
67687
+ display.onTextDelta(d2);
67688
+ },
67689
+ onToolCallStart: (d2) => {
67690
+ if (d2.subagentId) {
67691
+ subagents.addStreamingToolCall(d2.subagentId, d2.toolCallId, d2.toolName);
67692
+ return;
67693
+ }
67694
+ display.onToolCallStart(d2);
67695
+ },
67696
+ onToolCallDelta: (d2) => {
67697
+ if (d2.subagentId) {
67698
+ subagents.appendToolCallDelta(d2.subagentId, d2.toolCallId, d2.argsTextDelta);
67699
+ return;
67700
+ }
67701
+ display.onToolCallDelta(d2);
67702
+ },
67703
+ onToolCallComplete: (d2) => {
67704
+ if (d2.subagentId) {
67705
+ subagents.addToolCall(d2.subagentId, d2.toolCallId, d2.toolName, toRecordArgs(d2.args) ?? {});
67706
+ return;
67707
+ }
67708
+ display.onToolCallComplete(d2);
67709
+ deps.onRootToolCallStarted?.();
67710
+ if (d2.toolName === ASK_USER_QUESTIONS_TOOL_NAME) {
67711
+ const rawQuestions = toRecordArgs(d2.args)?.questions;
67712
+ if (Array.isArray(rawQuestions) && rawQuestions.length > 0) {
67713
+ deps.questions.onAsked(d2.toolCallId, rawQuestions);
67714
+ }
67715
+ }
67716
+ },
67717
+ onToolResult: (d2) => {
67718
+ if (d2.subagentId) {
67719
+ subagents.updateToolResult(d2.subagentId, d2.toolCallId, d2.result);
67720
+ return;
67721
+ }
67722
+ display.onToolResult(d2);
67723
+ if (d2.toolName === "submit_plan" && d2.result?.success === true) {
67724
+ deps.onPlanSubmitted?.();
67725
+ }
67726
+ },
67727
+ onCommandOutput: (d2) => {
67728
+ if (d2.subagentId)
67729
+ return;
67730
+ display.onCommandOutput(d2);
67731
+ },
67732
+ onError: (d2) => {
67733
+ if (d2.subagentId) {
67734
+ const errMsg = d2.error instanceof Error ? d2.error.message : "Unknown error";
67735
+ subagents.appendText(d2.subagentId, `
67736
+ Error: ${errMsg}
67737
+ `);
67738
+ return;
67739
+ }
67740
+ display.onError(d2);
67741
+ deps.questions.onCleared();
67742
+ },
67743
+ onSubagentSpawn: ({ subagentId, name }) => {
67744
+ if (subagentId.startsWith("app:"))
67745
+ return;
67746
+ subagents.spawnSession(subagentId, name);
67747
+ if (!isPentestAgent(subagentId))
67748
+ return;
67749
+ updateWorkflowData((wd) => applyWorkflowSubagentSpawn(wd, subagentId, name));
67750
+ },
67751
+ onSubagentComplete: ({ subagentId, status }) => {
67752
+ if (subagentId.startsWith("app:"))
67753
+ return;
67754
+ subagents.completeSession(subagentId, status);
67755
+ if (!isPentestAgent(subagentId))
67756
+ return;
67757
+ updateWorkflowData((wd) => applyWorkflowSubagentComplete(wd, subagentId, status));
67758
+ },
67759
+ onWorkflowPhaseStart: (d2) => {
67760
+ display.resetPartialText();
67761
+ if (d2.phase === "discovery") {
67762
+ deps.clearSubagentSessions();
67763
+ }
67764
+ updateWorkflowData((wd) => applyWorkflowPhaseStart(wd, d2.phase, d2.label));
67765
+ },
67766
+ onWorkflowPhaseComplete: (d2) => {
67767
+ display.resetPartialText();
67768
+ updateWorkflowData((wd) => applyWorkflowPhaseComplete(wd, d2.phase, d2.summary));
67769
+ }
67770
+ }
67771
+ };
67772
+ }
67773
+
67774
+ // src/core/integrations/wandb/upload.ts
67775
+ async function createWandbUploader(session, opts) {
67776
+ const resolved = resolveConfig(opts);
67777
+ if (!resolved.available)
67778
+ return null;
67779
+ const tracer = await createWeaveTracer(resolved.config);
67780
+ if (!tracer)
67781
+ return null;
67782
+ return {
67783
+ onRecord: (record) => tracer.logRecord(record, session.id),
67784
+ finalize: () => tracer.finish()
67785
+ };
67786
+ }
67787
+ async function attachWandbToEventBus(session, eventBus, opts) {
67788
+ const uploader = await createWandbUploader(session, opts);
67789
+ if (!uploader)
67790
+ return null;
67791
+ const handler = (e) => {
67792
+ uploader.onRecord(e.record);
67793
+ };
67794
+ eventBus.on("trace-record", handler);
67795
+ return async () => {
67796
+ eventBus.off("trace-record", handler);
67797
+ await uploader.finalize();
67798
+ };
67799
+ }
67800
+
67801
+ // src/tui/components/operator-dashboard/run-tracing.ts
67802
+ class RunTraceSession {
67803
+ eventBus;
67804
+ options;
67805
+ cleanup = null;
67806
+ earlyBuffer = [];
67807
+ countedSubagentSteps = new Set;
67808
+ earlyBufferHandler = (e) => {
67809
+ this.earlyBuffer?.push(e);
67810
+ };
67811
+ constructor(eventBus, options) {
67812
+ this.eventBus = eventBus;
67813
+ this.options = options;
67814
+ eventBus.on("trace-record", this.earlyBufferHandler);
67815
+ eventBus.on("trace-record", this.countSubagentStep);
67816
+ }
67817
+ async tryAttach(session) {
67818
+ if (this.cleanup)
67819
+ return;
67820
+ const attach = this.options.attach ?? attachWandbToEventBus;
67821
+ const cleanup = await attach(session, this.eventBus).catch((e) => {
67822
+ console.error("[wandb] Attach failed:", e);
67823
+ return null;
67824
+ });
67825
+ const buffered = this.earlyBuffer;
67826
+ this.stopBuffering();
67827
+ if (cleanup) {
67828
+ this.cleanup = cleanup;
67829
+ if (buffered) {
67830
+ for (const e of buffered)
67831
+ this.eventBus.emit("trace-record", e);
67832
+ }
67833
+ }
67834
+ }
67835
+ async cleanupRun() {
67836
+ this.stopBuffering();
67837
+ this.eventBus.off("trace-record", this.countSubagentStep);
67838
+ if (this.cleanup) {
67839
+ const fn = this.cleanup;
67840
+ this.cleanup = null;
67841
+ await fn().catch((e) => console.error("[wandb] Flush failed:", e));
67842
+ }
67843
+ }
67844
+ stopBuffering() {
67845
+ this.earlyBuffer = null;
67846
+ this.eventBus.off("trace-record", this.earlyBufferHandler);
67847
+ }
67848
+ countSubagentStep = (e) => {
67849
+ if (!this.options.isCurrent())
67850
+ return;
67851
+ const { record, subagentId } = e;
67852
+ if (!subagentId || record.type !== "step")
67853
+ return;
67854
+ const key = `${subagentId}:${record.stepIndex}:${record.timestamp}`;
67855
+ if (this.countedSubagentSteps.has(key))
67856
+ return;
67857
+ this.countedSubagentSteps.add(key);
67858
+ this.options.recordTokenUsage(record.usage.inputTokens, record.usage.outputTokens, record.usage.cacheReadTokens ?? 0, record.usage.cacheWriteTokens ?? 0);
67859
+ };
67860
+ }
67861
+
66844
67862
  // src/tui/components/operator-dashboard/subagent-dialog.tsx
66845
67863
  import { useKeyboard as useKeyboard25 } from "@opentui/react";
66846
67864
  import {
@@ -66848,7 +67866,7 @@ import {
66848
67866
  useMemo as useMemo21,
66849
67867
  useRef as useRef20,
66850
67868
  useState as useState38,
66851
- useSyncExternalStore
67869
+ useSyncExternalStore as useSyncExternalStore2
66852
67870
  } from "react";
66853
67871
 
66854
67872
  // src/tui/components/operator-dashboard/subagent-detail-view.tsx
@@ -66930,6 +67948,22 @@ function createSubagentStore() {
66930
67948
  setState
66931
67949
  };
66932
67950
  }
67951
+ function markSubagentsInterrupted(prev) {
67952
+ let changed = false;
67953
+ const next = new Map(prev);
67954
+ for (const [id, session] of next) {
67955
+ const hasInFlight = session.messages.some((m2) => m2.role === "tool" && (m2.status === "pending" || m2.status === "streaming"));
67956
+ if (!hasInFlight && session.status !== "running")
67957
+ continue;
67958
+ changed = true;
67959
+ next.set(id, {
67960
+ ...session,
67961
+ status: session.status === "running" ? "cancelled" : session.status,
67962
+ messages: session.messages.map((m2) => m2.role === "tool" && (m2.status === "pending" || m2.status === "streaming") ? { ...m2, status: "error", result: "Interrupted" } : m2)
67963
+ });
67964
+ }
67965
+ return changed ? next : prev;
67966
+ }
66933
67967
  function createSubagentSessionHelpers(setState) {
66934
67968
  const toolArgsDeltaBuffers = new Map;
66935
67969
  const spawnSession = (id, name, input) => {
@@ -67340,7 +68374,7 @@ function SubagentDialog({ store }) {
67340
68374
  const [view, setView] = useState38({ type: "hub" });
67341
68375
  const viewRef = useRef20(view);
67342
68376
  viewRef.current = view;
67343
- const sessions2 = useSyncExternalStore(store.subscribe, store.getSnapshot);
68377
+ const sessions2 = useSyncExternalStore2(store.subscribe, store.getSnapshot);
67344
68378
  useEffect24(() => {
67345
68379
  setSize(view.type === "detail" ? "xlarge" : "large");
67346
68380
  }, [view.type, setSize]);
@@ -67498,10 +68532,7 @@ var SubagentStatusBar = memo6(function SubagentStatusBar2({
67498
68532
  });
67499
68533
 
67500
68534
  // src/tui/components/operator-dashboard/index.tsx
67501
- import { jsxDEV as jsxDEV56, Fragment as Fragment6 } from "@opentui/react/jsx-dev-runtime";
67502
- function markInFlightToolsErrored(messages, result) {
67503
- return messages.map((m2) => isToolMessage(m2) && (m2.status === "pending" || m2.status === "streaming") ? { ...m2, status: "error", result } : m2);
67504
- }
68535
+ import { jsxDEV as jsxDEV56, Fragment as Fragment7 } from "@opentui/react/jsx-dev-runtime";
67505
68536
  function OperatorDashboard({
67506
68537
  sessionId,
67507
68538
  initialMessage,
@@ -67516,10 +68547,8 @@ function OperatorDashboard({
67516
68547
  isModelUserSelected,
67517
68548
  setThinking,
67518
68549
  setIsExecuting,
67519
- tokenUsage,
67520
- addTokenUsage,
67521
- addCacheUsage,
67522
- resetTokenUsage,
68550
+ usageStore,
68551
+ markExecuted,
67523
68552
  setSessionCwd,
67524
68553
  reasoningEnabled,
67525
68554
  openAIReasoningEffort
@@ -67579,7 +68608,7 @@ function OperatorDashboard({
67579
68608
  });
67580
68609
  const commandCancelledRef = useRef21(false);
67581
68610
  const subagentStore = useMemo22(() => createSubagentStore(), []);
67582
- const subagentSessions = useSyncExternalStore2(subagentStore.subscribe, subagentStore.getSnapshot);
68611
+ const subagentSessions = useSyncExternalStore3(subagentStore.subscribe, subagentStore.getSnapshot);
67583
68612
  const subagentHelpers = useMemo22(() => createSubagentSessionHelpers(subagentStore.setState), [subagentStore]);
67584
68613
  const messageCountAtSubagentDoneRef = useRef21(null);
67585
68614
  const hasRunningSubagent = useMemo22(() => Array.from(subagentSessions.values()).some((s) => s.status === "running"), [subagentSessions]);
@@ -67592,7 +68621,7 @@ function OperatorDashboard({
67592
68621
  messageCountAtSubagentDoneRef.current = null;
67593
68622
  }
67594
68623
  }, [subagentSessions, hasRunningSubagent]);
67595
- const openSubagentDialog = useCallback20(() => {
68624
+ const openSubagentDialog = useCallback22(() => {
67596
68625
  replaceDialog(/* @__PURE__ */ jsxDEV56(SubagentDialog, {
67597
68626
  store: subagentStore
67598
68627
  }, undefined, false, undefined, this), {
@@ -67603,7 +68632,6 @@ function OperatorDashboard({
67603
68632
  const [messages, setMessages] = useState39([]);
67604
68633
  const displayMessagesRef = useRef21([]);
67605
68634
  displayMessagesRef.current = messages;
67606
- const textRef = useRef21("");
67607
68635
  const conversationRef = useRef21([]);
67608
68636
  const [inputValue, setInputValue] = useState39("");
67609
68637
  const [queuedMessages, setQueuedMessages] = useState39([]);
@@ -67644,10 +68672,6 @@ function OperatorDashboard({
67644
68672
  useEffect25(() => {
67645
68673
  approvedPlanRef.current = approvedPlanContent;
67646
68674
  }, [approvedPlanContent]);
67647
- const tokenUsageRef = useRef21(tokenUsage);
67648
- useEffect25(() => {
67649
- tokenUsageRef.current = tokenUsage;
67650
- }, [tokenUsage]);
67651
68675
  useEffect25(() => {
67652
68676
  const gate = approvalGateRef.current;
67653
68677
  const onApprovalNeeded = () => {
@@ -67766,188 +68790,63 @@ function OperatorDashboard({
67766
68790
  refocusPrompt();
67767
68791
  }
67768
68792
  }, [loading, refocusPrompt]);
68793
+ useEffect25(() => {
68794
+ if (sessionId === undefined)
68795
+ usageStore.beginNewSession();
68796
+ }, [sessionId, usageStore]);
67769
68797
  useEffect25(() => {
67770
68798
  if (!session || !sessionId)
67771
68799
  return;
67772
- resetTokenUsage();
67773
- tokenUsageRef.current = {
67774
- inputTokens: 0,
67775
- outputTokens: 0,
67776
- totalTokens: 0,
67777
- cachedTokens: 0,
67778
- cacheWriteTokens: 0
67779
- };
67780
68800
  const metrics = readExecutionMetrics(session.rootPath);
67781
- const persisted = metrics?.tokenUsage;
67782
- if (persisted && (persisted.inputTokens > 0 || persisted.outputTokens > 0)) {
67783
- addTokenUsage(persisted.inputTokens, persisted.outputTokens);
67784
- tokenUsageRef.current = {
67785
- ...persisted,
67786
- cachedTokens: 0,
67787
- cacheWriteTokens: 0
67788
- };
67789
- }
67790
- try {
67791
- writeExecutionMetrics({
67792
- sessionRootPath: session.rootPath,
67793
- tokenUsage: tokenUsageRef.current
67794
- });
67795
- } catch {}
67796
- }, [session, sessionId, addTokenUsage, resetTokenUsage]);
67797
- const appendText = useCallback20((text) => {
67798
- textRef.current += text;
67799
- const accumulated = textRef.current;
67800
- setMessages((prev) => {
67801
- const last = prev[prev.length - 1];
67802
- if (last && last.role === "assistant") {
67803
- const updated = [...prev];
67804
- updated[updated.length - 1] = { ...last, content: accumulated };
67805
- return updated;
67806
- }
67807
- return [
67808
- ...prev,
67809
- { role: "assistant", content: accumulated, createdAt: new Date }
67810
- ];
67811
- });
67812
- }, []);
67813
- const toolArgsDeltaRef = useRef21(new Map);
67814
- const addStreamingToolCall = useCallback20((toolCallId, toolName) => {
67815
- textRef.current = "";
67816
- toolArgsDeltaRef.current.set(toolCallId, {
67817
- toolName,
67818
- accumulated: ""
67819
- });
67820
- setMessages((prev) => [
67821
- ...prev,
67822
- {
67823
- role: "tool",
67824
- content: "",
67825
- createdAt: new Date,
67826
- toolCallId,
67827
- toolName,
67828
- args: {},
67829
- status: "streaming"
67830
- }
67831
- ]);
67832
- }, []);
67833
- const appendToolCallDelta = useCallback20((toolCallId, argsTextDelta) => {
67834
- const entry = toolArgsDeltaRef.current.get(toolCallId);
67835
- const accumulated = (entry?.accumulated ?? "") + argsTextDelta;
67836
- toolArgsDeltaRef.current.set(toolCallId, {
67837
- toolName: entry?.toolName ?? "",
67838
- accumulated
67839
- });
67840
- const parsed = tryParsePartialJson(accumulated);
67841
- if (!parsed)
67842
- return;
67843
- const contentText = extractStreamableContent(parsed);
67844
- const logs = contentText ? contentText.split(`
67845
- `) : undefined;
67846
- setMessages((msgs) => {
67847
- const idx = msgs.findIndex((m2) => isToolMessage(m2) && m2.toolCallId === toolCallId);
67848
- if (idx === -1)
67849
- return msgs;
67850
- const updated = [...msgs];
67851
- updated[idx] = { ...updated[idx], args: parsed, ...logs && { logs } };
67852
- return updated;
67853
- });
67854
- }, []);
67855
- const addToolCall = useCallback20((toolCallId, toolName, args) => {
67856
- textRef.current = "";
67857
- toolArgsDeltaRef.current.delete(toolCallId);
67858
- setMessages((prev) => {
67859
- const idx = prev.findIndex((m2) => isToolMessage(m2) && m2.toolCallId === toolCallId);
67860
- if (idx !== -1) {
67861
- const updated = [...prev];
67862
- updated[idx] = {
67863
- ...updated[idx],
67864
- args,
67865
- logs: undefined,
67866
- status: "pending"
67867
- };
67868
- return updated;
67869
- }
67870
- return [
67871
- ...prev,
67872
- {
67873
- role: "tool",
67874
- content: "",
67875
- createdAt: new Date,
67876
- toolCallId,
67877
- toolName,
67878
- args,
67879
- status: "pending"
67880
- }
67881
- ];
68801
+ usageStore.enterSession(session.id, {
68802
+ ...metrics?.tokenUsage ? { tokenUsage: metrics.tokenUsage } : {},
68803
+ ...metrics?.contextUsage ? { contextUsage: metrics.contextUsage } : {}
67882
68804
  });
67883
- }, []);
67884
- const updateToolResult = useCallback20((toolCallId, _toolName, result) => {
67885
- textRef.current = "";
67886
- setMessages((prev) => {
67887
- const idx = prev.findIndex((m2) => isToolMessage(m2) && m2.toolCallId === toolCallId);
67888
- if (idx === -1)
67889
- return prev;
67890
- const updated = [...prev];
67891
- updated[idx] = { ...updated[idx], status: "completed", result };
67892
- return updated;
67893
- });
67894
- }, []);
67895
- const cmdOutputBufRef = useRef21("");
67896
- const cmdFlushTimerRef = useRef21(null);
67897
- const MAX_LOG_LINES = 200;
67898
- const flushCommandOutput = useCallback20(() => {
67899
- const buf = cmdOutputBufRef.current;
67900
- if (!buf)
67901
- return;
67902
- cmdOutputBufRef.current = "";
67903
- setMessages((prev) => {
67904
- const idx = prev.findLastIndex((m2) => isToolMessage(m2) && (m2.status === "pending" || m2.status === "streaming"));
67905
- if (idx === -1)
67906
- return prev;
67907
- const msg = prev[idx];
67908
- const existing = msg.logs ?? [];
67909
- const incoming = buf.split(`
67910
- `);
67911
- let merged;
67912
- if (existing.length > 0 && !buf.startsWith(`
67913
- `)) {
67914
- merged = [...existing];
67915
- merged[merged.length - 1] += incoming[0];
67916
- merged.push(...incoming.slice(1));
67917
- } else {
67918
- merged = [...existing, ...incoming];
67919
- }
67920
- if (merged.length > MAX_LOG_LINES) {
67921
- merged = merged.slice(-MAX_LOG_LINES);
67922
- }
67923
- const updated = [...prev];
67924
- updated[idx] = { ...msg, logs: merged };
67925
- return updated;
67926
- });
67927
- }, []);
67928
- const onCommandOutput = useCallback20((data) => {
67929
- cmdOutputBufRef.current += data;
67930
- if (!cmdFlushTimerRef.current) {
67931
- cmdFlushTimerRef.current = setInterval(() => {
67932
- flushCommandOutput();
67933
- }, 150);
67934
- }
67935
- }, [flushCommandOutput]);
68805
+ }, [session, sessionId, usageStore]);
68806
+ const runSessionIdRef = useRef21(null);
68807
+ const displayEvents = useMemo22(() => createDisplayEventHandlers({
68808
+ updateMessages: setMessages,
68809
+ setThinking,
68810
+ setError
68811
+ }), [setThinking]);
67936
68812
  useEffect25(() => {
67937
68813
  return () => {
67938
- if (cmdFlushTimerRef.current) {
67939
- clearInterval(cmdFlushTimerRef.current);
67940
- cmdFlushTimerRef.current = null;
67941
- }
68814
+ displayEvents.dispose();
67942
68815
  };
68816
+ }, [displayEvents]);
68817
+ const updateWorkflowData = useCallback22((updater) => {
68818
+ setMessages((prev) => updateWorkflowDataMessage(prev, updater));
67943
68819
  }, []);
67944
- const handleApprove = useCallback20(() => {
68820
+ const runEventProjections = useMemo22(() => createRunEventProjections({
68821
+ display: displayEvents,
68822
+ subagents: subagentHelpers,
68823
+ updateWorkflowData,
68824
+ clearSubagentSessions: () => subagentStore.setState(new Map),
68825
+ questions: {
68826
+ onAsked: (toolCallId, questions) => {
68827
+ pendingToolCallIdRef.current = toolCallId;
68828
+ setPendingQuestions(questions);
68829
+ },
68830
+ onCleared: () => {
68831
+ pendingToolCallIdRef.current = null;
68832
+ setPendingQuestions(null);
68833
+ }
68834
+ },
68835
+ onRootToolCallStarted: () => {
68836
+ commandCancelledRef.current = false;
68837
+ },
68838
+ onPlanSubmitted: () => {
68839
+ planSubmittedRef.current = true;
68840
+ planGateBypassedOnResumeRef.current = false;
68841
+ }
68842
+ }), [displayEvents, subagentHelpers, updateWorkflowData, subagentStore]);
68843
+ const handleApprove = useCallback22(() => {
67945
68844
  const pending = approvalGateRef.current.getPendingApprovals();
67946
68845
  if (pending.length > 0) {
67947
68846
  approvalGateRef.current.approve(pending[0].id);
67948
68847
  }
67949
68848
  }, []);
67950
- const handleAutoApprove = useCallback20(() => {
68849
+ const handleAutoApprove = useCallback22(() => {
67951
68850
  setOperatorMode("auto");
67952
68851
  setOperatorState((prev) => ({
67953
68852
  ...prev,
@@ -67960,17 +68859,18 @@ function OperatorDashboard({
67960
68859
  approvalGateRef.current.approve(p.id);
67961
68860
  }
67962
68861
  }, []);
67963
- const runAgent = useCallback20(async (prompt) => {
68862
+ const runAgent = useCallback22(async (prompt) => {
67964
68863
  if (abortControllerRef.current) {
67965
68864
  abortControllerRef.current.abort();
67966
68865
  abortControllerRef.current = null;
67967
68866
  }
67968
68867
  const gen = ++generationRef.current;
68868
+ runSessionIdRef.current = sessionRef.current?.id ?? session?.id ?? null;
67969
68869
  setStatus("running");
67970
68870
  setThinking(true);
67971
68871
  setIsExecuting(true);
67972
68872
  setError(null);
67973
- textRef.current = "";
68873
+ displayEvents.resetPartialText();
67974
68874
  const controller = new AbortController;
67975
68875
  abortControllerRef.current = controller;
67976
68876
  const prevMessages = conversationRef.current;
@@ -67987,259 +68887,58 @@ function OperatorDashboard({
67987
68887
  conversationRef.current = nextMessages;
67988
68888
  if (sessionRef.current) {
67989
68889
  try {
67990
- const mp = join3(sessionRef.current.rootPath, "messages.json");
67991
- writeFileSync(mp, JSON.stringify(nextMessages, null, 2));
68890
+ const mp = join4(sessionRef.current.rootPath, "messages.json");
68891
+ writeFileSync2(mp, JSON.stringify(nextMessages, null, 2));
67992
68892
  } catch {}
67993
68893
  }
67994
68894
  } else {
67995
68895
  nextMessages = conversationRef.current;
67996
68896
  }
67997
68897
  const recordTokenUsage = (inputTokens, outputTokens, cacheReadTokens = 0, cacheWriteTokens = 0) => {
67998
- const nextUsage = accumulateTokenUsage(tokenUsageRef.current, inputTokens, outputTokens);
67999
- if (nextUsage) {
68000
- tokenUsageRef.current = nextUsage;
68001
- addTokenUsage(inputTokens, outputTokens);
68002
- if (session) {
68003
- try {
68004
- writeExecutionMetrics({
68005
- sessionRootPath: session.rootPath,
68006
- tokenUsage: nextUsage
68007
- });
68008
- } catch {}
68009
- }
68898
+ if (inputTokens > 0 || outputTokens > 0)
68899
+ markExecuted();
68900
+ const runSessionId = runSessionIdRef.current;
68901
+ usageStore.addSessionTokens(runSessionId, {
68902
+ inputTokens,
68903
+ outputTokens,
68904
+ cacheReadTokens,
68905
+ cacheWriteTokens
68906
+ });
68907
+ const activeSession = sessionRef.current;
68908
+ if (activeSession) {
68909
+ try {
68910
+ const usage = usageStore.getSnapshot(activeSession.id);
68911
+ writeExecutionMetrics({
68912
+ sessionRootPath: activeSession.rootPath,
68913
+ tokenUsage: usage.tokenUsage,
68914
+ ...usage.contextUsage ? { contextUsage: usage.contextUsage } : {}
68915
+ });
68916
+ } catch {}
68010
68917
  }
68011
- if (cacheReadTokens > 0 || cacheWriteTokens > 0) {
68012
- addCacheUsage(cacheReadTokens, cacheWriteTokens);
68918
+ };
68919
+ const runModelId = model.id;
68920
+ const recordRootStepUsage = (usage) => {
68921
+ const inputTokens = usage.inputTokens ?? 0;
68922
+ if (inputTokens > 0) {
68923
+ usageStore.setRootContext(runSessionIdRef.current, {
68924
+ usedTokens: inputTokens,
68925
+ contextLimit: getContextWindow(runModelId),
68926
+ modelId: runModelId
68927
+ });
68013
68928
  }
68929
+ recordTokenUsage(inputTokens, usage.outputTokens ?? 0);
68014
68930
  };
68015
68931
  const onStepFinish = (event) => {
68016
- recordTokenUsage(event.usage?.inputTokens ?? 0, event.usage?.outputTokens ?? 0);
68932
+ recordRootStepUsage(event.usage ?? {});
68017
68933
  };
68018
68934
  const eventBus = new AgentEventBus;
68019
- const isPentestAgent = (id) => id !== undefined && /^pentest-agent-\d+$/.test(id);
68020
- const updateWorkflowData = (updater) => {
68021
- setMessages((prev) => {
68022
- const idx = prev.findLastIndex((m2) => isToolMessage(m2) && m2.toolName === "run_pentest_workflow" && (m2.status === "pending" || m2.status === "streaming"));
68023
- if (idx === -1)
68024
- return prev;
68025
- const msg = prev[idx];
68026
- const wd = msg.workflowData ?? {
68027
- currentPhase: "discovery",
68028
- discovery: { label: "", status: "pending", logs: [] },
68029
- pentesting: {
68030
- label: "",
68031
- status: "idle",
68032
- subagents: {}
68033
- },
68034
- reporting: { label: "", status: "idle" }
68035
- };
68036
- const updated = [...prev];
68037
- updated[idx] = { ...msg, workflowData: updater(wd) };
68038
- return updated;
68039
- });
68040
- };
68041
- eventBus.on("text-delta", (d2) => {
68042
- if (gen !== generationRef.current)
68043
- return;
68044
- if (d2.subagentId) {
68045
- subagentHelpers.appendText(d2.subagentId, d2.text);
68046
- return;
68047
- }
68048
- setThinking(false);
68049
- appendText(d2.text);
68050
- });
68051
- eventBus.on("tool-call-start", (d2) => {
68052
- if (gen !== generationRef.current)
68053
- return;
68054
- if (d2.subagentId) {
68055
- subagentHelpers.addStreamingToolCall(d2.subagentId, d2.toolCallId, d2.toolName);
68056
- return;
68057
- }
68058
- setThinking(false);
68059
- addStreamingToolCall(d2.toolCallId, d2.toolName);
68060
- });
68061
- eventBus.on("tool-call-delta", (d2) => {
68062
- if (gen !== generationRef.current)
68063
- return;
68064
- if (d2.subagentId) {
68065
- subagentHelpers.appendToolCallDelta(d2.subagentId, d2.toolCallId, d2.argsTextDelta);
68066
- return;
68067
- }
68068
- appendToolCallDelta(d2.toolCallId, d2.argsTextDelta);
68935
+ const runTrace = new RunTraceSession(eventBus, {
68936
+ isCurrent: () => gen === generationRef.current,
68937
+ recordTokenUsage
68069
68938
  });
68070
- eventBus.on("tool-call-complete", (d2) => {
68071
- if (gen !== generationRef.current)
68072
- return;
68073
- if (d2.subagentId) {
68074
- const args2 = d2.args && typeof d2.args === "object" && !Array.isArray(d2.args) && d2.args !== null ? d2.args : {};
68075
- subagentHelpers.addToolCall(d2.subagentId, d2.toolCallId, d2.toolName, args2);
68076
- return;
68077
- }
68078
- setThinking(false);
68079
- commandCancelledRef.current = false;
68080
- const args = d2.args && typeof d2.args === "object" && !Array.isArray(d2.args) && d2.args !== null ? d2.args : undefined;
68081
- addToolCall(d2.toolCallId, d2.toolName, args);
68082
- if (d2.toolName === ASK_USER_QUESTIONS_TOOL_NAME && args) {
68083
- const rawQuestions = args.questions;
68084
- if (Array.isArray(rawQuestions) && rawQuestions.length > 0) {
68085
- pendingToolCallIdRef.current = d2.toolCallId;
68086
- setPendingQuestions(rawQuestions);
68087
- }
68088
- }
68089
- });
68090
- eventBus.on("tool-result", (d2) => {
68091
- if (gen !== generationRef.current)
68092
- return;
68093
- if (d2.subagentId) {
68094
- subagentHelpers.updateToolResult(d2.subagentId, d2.toolCallId, d2.result);
68095
- return;
68096
- }
68097
- flushCommandOutput();
68098
- if (cmdFlushTimerRef.current) {
68099
- clearInterval(cmdFlushTimerRef.current);
68100
- cmdFlushTimerRef.current = null;
68101
- }
68102
- setThinking(true);
68103
- updateToolResult(d2.toolCallId, d2.toolName, d2.result);
68104
- if (d2.toolName === "submit_plan" && d2.result?.success === true) {
68105
- planSubmittedRef.current = true;
68106
- planGateBypassedOnResumeRef.current = false;
68107
- }
68108
- });
68109
- eventBus.on("command-output", (d2) => {
68110
- if (gen !== generationRef.current)
68111
- return;
68112
- if (d2.subagentId)
68113
- return;
68114
- onCommandOutput(d2.data);
68115
- });
68116
- eventBus.on("error", (d2) => {
68117
- if (gen !== generationRef.current)
68118
- return;
68119
- if (d2.subagentId) {
68120
- const errMsg = d2.error instanceof Error ? d2.error.message : "Unknown error";
68121
- subagentHelpers.appendText(d2.subagentId, `
68122
- Error: ${errMsg}
68123
- `);
68124
- return;
68125
- }
68126
- console.error("Agent error:", d2.error);
68127
- pendingToolCallIdRef.current = null;
68128
- setPendingQuestions(null);
68129
- const errorMessage = d2.error instanceof Error ? d2.error.message : "Unknown error";
68130
- setError(errorMessage);
68131
- setMessages((prev) => markInFlightToolsErrored(prev, errorMessage));
68132
- });
68133
- eventBus.on("subagent-spawn", ({ subagentId, name }) => {
68134
- if (gen !== generationRef.current)
68135
- return;
68136
- if (subagentId.startsWith("app:"))
68137
- return;
68138
- subagentHelpers.spawnSession(subagentId, name);
68139
- if (!isPentestAgent(subagentId))
68140
- return;
68141
- updateWorkflowData((wd) => ({
68142
- ...wd,
68143
- pentesting: {
68144
- ...wd.pentesting,
68145
- subagents: {
68146
- ...wd.pentesting.subagents,
68147
- [subagentId]: {
68148
- name,
68149
- status: "pending",
68150
- logs: []
68151
- }
68152
- }
68153
- }
68154
- }));
68155
- });
68156
- eventBus.on("subagent-complete", ({ subagentId, status: status2 }) => {
68157
- if (gen !== generationRef.current)
68158
- return;
68159
- if (subagentId.startsWith("app:"))
68160
- return;
68161
- subagentHelpers.completeSession(subagentId, status2);
68162
- if (!isPentestAgent(subagentId))
68163
- return;
68164
- updateWorkflowData((wd) => {
68165
- const entry = wd.pentesting.subagents[subagentId];
68166
- if (!entry)
68167
- return wd;
68168
- return {
68169
- ...wd,
68170
- pentesting: {
68171
- ...wd.pentesting,
68172
- subagents: {
68173
- ...wd.pentesting.subagents,
68174
- [subagentId]: { ...entry, status: status2 }
68175
- }
68176
- }
68177
- };
68178
- });
68179
- });
68180
- eventBus.on("workflow-phase-start", (d2) => {
68181
- if (gen !== generationRef.current)
68182
- return;
68183
- textRef.current = "";
68184
- if (d2.phase === "discovery") {
68185
- subagentStore.setState(new Map);
68186
- }
68187
- updateWorkflowData((wd) => {
68188
- const next = {
68189
- ...wd,
68190
- currentPhase: d2.phase
68191
- };
68192
- if (d2.phase === "discovery") {
68193
- next.discovery = {
68194
- ...wd.discovery,
68195
- label: d2.label,
68196
- status: "pending",
68197
- logs: []
68198
- };
68199
- } else if (d2.phase === "pentesting") {
68200
- next.pentesting = {
68201
- ...wd.pentesting,
68202
- label: d2.label,
68203
- status: "pending"
68204
- };
68205
- } else if (d2.phase === "reporting") {
68206
- next.reporting = {
68207
- ...wd.reporting,
68208
- label: d2.label,
68209
- status: "pending"
68210
- };
68211
- }
68212
- return next;
68213
- });
68214
- });
68215
- eventBus.on("workflow-phase-complete", (d2) => {
68216
- if (gen !== generationRef.current)
68217
- return;
68218
- textRef.current = "";
68219
- updateWorkflowData((wd) => {
68220
- const next = { ...wd };
68221
- if (d2.phase === "discovery") {
68222
- const targets = d2.summary.targets ?? [];
68223
- next.discovery = {
68224
- ...wd.discovery,
68225
- status: "complete",
68226
- targets,
68227
- cached: d2.summary.cached
68228
- };
68229
- } else if (d2.phase === "pentesting") {
68230
- next.pentesting = { ...wd.pentesting, status: "complete" };
68231
- } else if (d2.phase === "reporting") {
68232
- next.reporting = {
68233
- ...wd.reporting,
68234
- status: "complete",
68235
- findingsCount: d2.summary.findingsCount,
68236
- findingsBySeverity: d2.summary.findingsBySeverity,
68237
- reportPath: d2.summary.reportPath
68238
- };
68239
- next.currentPhase = "complete";
68240
- }
68241
- return next;
68242
- });
68939
+ const unbindRunEvents = bindOperatorRunEvents(eventBus, {
68940
+ isCurrent: () => gen === generationRef.current,
68941
+ handlers: runEventProjections.handlers
68243
68942
  });
68244
68943
  const skillsCatalog = skillsRegistry.buildCatalog() || undefined;
68245
68944
  const commonInput = {
@@ -68267,59 +68966,24 @@ Error: ${errMsg}
68267
68966
  promptInjectionLibrarySource: initialConfig?.promptInjectionLibrarySource ?? (route.data.type === "operator" ? route.data.initialSkill?.args?.library : undefined),
68268
68967
  onStepFinish,
68269
68968
  onCacheMetrics: (metrics) => {
68270
- addCacheUsage(metrics.cacheReadInputTokens, metrics.cacheCreationInputTokens);
68969
+ usageStore.addSessionTokens(runSessionIdRef.current, {
68970
+ cacheReadTokens: metrics.cacheReadInputTokens,
68971
+ cacheWriteTokens: metrics.cacheCreationInputTokens
68972
+ });
68271
68973
  },
68272
68974
  eventBus,
68273
68975
  onSessionReady: (s) => {
68274
68976
  setSessionCwd(s.rootPath);
68275
68977
  sessionRef.current = s;
68276
68978
  setSession((prev) => prev ?? s);
68277
- tryAttachWandb(s);
68278
- }
68279
- };
68280
- let wandbCleanup = null;
68281
- let earlyBuffer = [];
68282
- const earlyBufferHandler = (e) => {
68283
- earlyBuffer?.push(e);
68284
- };
68285
- eventBus.on("trace-record", earlyBufferHandler);
68286
- const countedSubagentSteps = new Set;
68287
- eventBus.on("trace-record", ({ record, subagentId }) => {
68288
- if (gen !== generationRef.current)
68289
- return;
68290
- if (!subagentId || record.type !== "step")
68291
- return;
68292
- const key = `${subagentId}:${record.stepIndex}:${record.timestamp}`;
68293
- if (countedSubagentSteps.has(key))
68294
- return;
68295
- countedSubagentSteps.add(key);
68296
- recordTokenUsage(record.usage.inputTokens, record.usage.outputTokens, record.usage.cacheReadTokens ?? 0, record.usage.cacheWriteTokens ?? 0);
68297
- });
68298
- const tryAttachWandb = async (s) => {
68299
- if (wandbCleanup)
68300
- return;
68301
- const cleanup = await attachWandbToEventBus(s, eventBus).catch((e) => {
68302
- console.error("[wandb] Attach failed:", e);
68303
- return null;
68304
- });
68305
- if (cleanup) {
68306
- wandbCleanup = cleanup;
68307
- const buffered = earlyBuffer;
68308
- earlyBuffer = null;
68309
- eventBus.off("trace-record", earlyBufferHandler);
68310
- if (buffered) {
68311
- for (const e of buffered) {
68312
- eventBus.emit("trace-record", e);
68313
- }
68314
- }
68315
- } else {
68316
- earlyBuffer = null;
68317
- eventBus.off("trace-record", earlyBufferHandler);
68979
+ runSessionIdRef.current = s.id;
68980
+ usageStore.enterSession(s.id);
68981
+ runTrace.tryAttach(s);
68318
68982
  }
68319
68983
  };
68320
68984
  const wandbSession = session ?? sessionRef.current;
68321
68985
  if (wandbSession) {
68322
- await tryAttachWandb(wandbSession);
68986
+ await runTrace.tryAttach(wandbSession);
68323
68987
  }
68324
68988
  try {
68325
68989
  let agentResult;
@@ -68374,9 +69038,9 @@ Error: ${errMsg}
68374
69038
  if (gen === generationRef.current) {
68375
69039
  try {
68376
69040
  const rootPath = agentResult.session.rootPath;
68377
- const mp = join3(rootPath, "messages.json");
68378
- if (existsSync3(mp)) {
68379
- const raw = JSON.parse(readFileSync3(mp, "utf-8"));
69041
+ const mp = join4(rootPath, "messages.json");
69042
+ if (existsSync4(mp)) {
69043
+ const raw = JSON.parse(readFileSync4(mp, "utf-8"));
68380
69044
  if (Array.isArray(raw) && raw.length > 0) {
68381
69045
  conversationRef.current = normalizeMessages(sessions.getResumeMessages(raw));
68382
69046
  }
@@ -68402,8 +69066,8 @@ Error: ${errMsg}
68402
69066
  setPendingQuestions(null);
68403
69067
  if (sessionRef.current) {
68404
69068
  try {
68405
- const mp = join3(sessionRef.current.rootPath, "messages.json");
68406
- writeFileSync(mp, JSON.stringify(prevMessages, null, 2));
69069
+ const mp = join4(sessionRef.current.rootPath, "messages.json");
69070
+ writeFileSync2(mp, JSON.stringify(prevMessages, null, 2));
68407
69071
  } catch {}
68408
69072
  }
68409
69073
  const errorMsg = e instanceof Error ? e.message : "Agent failed";
@@ -68418,12 +69082,8 @@ Error: ${errMsg}
68418
69082
  ]);
68419
69083
  }
68420
69084
  } finally {
68421
- earlyBuffer = null;
68422
- eventBus.off("trace-record", earlyBufferHandler);
68423
- if (wandbCleanup) {
68424
- const fn = wandbCleanup;
68425
- await fn().catch((e) => console.error("[wandb] Flush failed:", e));
68426
- }
69085
+ unbindRunEvents();
69086
+ await runTrace.cleanupRun();
68427
69087
  if (gen === generationRef.current) {
68428
69088
  setStatus(pendingToolCallIdRef.current ? "waiting" : "idle");
68429
69089
  setThinking(false);
@@ -68442,18 +69102,10 @@ Error: ${errMsg}
68442
69102
  operatorState,
68443
69103
  operatorMode,
68444
69104
  agentMode,
68445
- addTokenUsage,
68446
- appendText,
68447
- addStreamingToolCall,
68448
- appendToolCallDelta,
68449
- addToolCall,
68450
- updateToolResult,
68451
- flushCommandOutput,
68452
- onCommandOutput,
68453
- subagentHelpers,
69105
+ displayEvents,
69106
+ runEventProjections,
68454
69107
  setThinking,
68455
69108
  setIsExecuting,
68456
- addCacheUsage,
68457
69109
  initialConfig?.sandbox,
68458
69110
  initialConfig?.taskDriven,
68459
69111
  initialConfig?.target,
@@ -68462,14 +69114,15 @@ Error: ${errMsg}
68462
69114
  strikeMode,
68463
69115
  route.data,
68464
69116
  setSessionCwd,
68465
- subagentStore.setState,
68466
69117
  skillsRegistry.buildCatalog,
68467
69118
  requireApproval,
68468
69119
  skillsRegistry,
68469
69120
  reasoningEnabled,
68470
- openAIReasoningEffort
69121
+ openAIReasoningEffort,
69122
+ usageStore,
69123
+ markExecuted
68471
69124
  ]);
68472
- const handleSubmit = useCallback20((value) => {
69125
+ const handleSubmit = useCallback22((value) => {
68473
69126
  const pending = approvalGateRef.current.getPendingApprovals();
68474
69127
  const result = resolveSubmit(value, status, pending.length > 0);
68475
69128
  if (result.denyPending) {
@@ -68607,16 +69260,16 @@ ${content}
68607
69260
  planApprovedPendingRunRef.current = false;
68608
69261
  runAgentRef.current("Proceed with the approved plan.");
68609
69262
  }, [operatorMode]);
68610
- const addSystemMessage = useCallback20((content) => {
69263
+ const addSystemMessage = useCallback22((content) => {
68611
69264
  setMessages((prev) => [
68612
69265
  ...prev,
68613
69266
  { role: "system", content, createdAt: new Date }
68614
69267
  ]);
68615
69268
  }, []);
68616
- const showModelPicker = useCallback20(() => {
69269
+ const showModelPicker = useCallback22(() => {
68617
69270
  executeCommand("/models");
68618
69271
  }, [executeCommand]);
68619
- const handleHeadersSlash = useCallback20(async (op) => {
69272
+ const handleHeadersSlash = useCallback22(async (op) => {
68620
69273
  const active = sessionRef.current;
68621
69274
  if (!active) {
68622
69275
  addSystemMessage("No active session — cannot manage headers yet.");
@@ -68687,7 +69340,7 @@ ${formatParseError(parsed.error)}`);
68687
69340
  }
68688
69341
  }
68689
69342
  }, [addSystemMessage]);
68690
- const handleCommandExecute = useCallback20(async (command) => {
69343
+ const handleCommandExecute = useCallback22(async (command) => {
68691
69344
  const action = routeCommand(command, resolveSkillContent);
68692
69345
  switch (action.type) {
68693
69346
  case "show-models":
@@ -68761,7 +69414,7 @@ ${planContent}` : "No plan exists yet. Switch to plan mode (Shift+Tab) to create
68761
69414
  handleHeadersSlash,
68762
69415
  strikeMode
68763
69416
  ]);
68764
- const handleAbort = useCallback20(() => {
69417
+ const handleAbort = useCallback22(() => {
68765
69418
  if (!abortControllerRef.current)
68766
69419
  return;
68767
69420
  const action = resolveAbortAction(commandCancelledRef.current, () => cancelHandleRef.current.cancel());
@@ -68789,77 +69442,15 @@ ${planContent}` : "No plan exists yet. Switch to plan mode (Shift+Tab) to create
68789
69442
  setThinking(false);
68790
69443
  setIsExecuting(false);
68791
69444
  setMessages((prev) => markInFlightToolsErrored(prev, "Interrupted"));
68792
- subagentStore.setState((prev) => {
68793
- let changed = false;
68794
- const next = new Map(prev);
68795
- for (const [id, session2] of next) {
68796
- const hasInFlight = session2.messages.some((m2) => m2.role === "tool" && (m2.status === "pending" || m2.status === "streaming"));
68797
- if (hasInFlight || session2.status === "running") {
68798
- changed = true;
68799
- next.set(id, {
68800
- ...session2,
68801
- status: session2.status === "running" ? "cancelled" : session2.status,
68802
- messages: session2.messages.map((m2) => m2.role === "tool" && (m2.status === "pending" || m2.status === "streaming") ? { ...m2, status: "error", result: "Interrupted" } : m2)
68803
- });
68804
- }
68805
- }
68806
- return changed ? next : prev;
68807
- });
69445
+ subagentStore.setState(markSubagentsInterrupted);
68808
69446
  const activeSession = sessionRef.current;
68809
69447
  if (activeSession) {
68810
- try {
68811
- const messagesPath = join3(activeSession.rootPath, "messages.json");
68812
- if (existsSync3(messagesPath)) {
68813
- const raw = JSON.parse(readFileSync3(messagesPath, "utf-8"));
68814
- if (Array.isArray(raw) && raw.length > 0) {
68815
- conversationRef.current = normalizeMessages(sessions.getResumeMessages(raw));
68816
- }
68817
- }
68818
- const last = conversationRef.current[conversationRef.current.length - 1];
68819
- if (last?.role === "user") {
68820
- const partial = textRef.current.trim();
68821
- const pendingTools = displayMessagesRef.current.filter((m2) => isToolMessage(m2) && (m2.status === "pending" || m2.status === "streaming"));
68822
- const assistantContent = [
68823
- {
68824
- type: "text",
68825
- text: partial || "[Response interrupted by user.]"
68826
- }
68827
- ];
68828
- for (const t of pendingTools) {
68829
- assistantContent.push({
68830
- type: "tool-call",
68831
- toolCallId: t.toolCallId,
68832
- toolName: t.toolName,
68833
- input: t.args ?? {}
68834
- });
68835
- }
68836
- conversationRef.current = [
68837
- ...conversationRef.current,
68838
- {
68839
- role: "assistant",
68840
- content: assistantContent
68841
- }
68842
- ];
68843
- if (pendingTools.length > 0) {
68844
- conversationRef.current = [
68845
- ...conversationRef.current,
68846
- {
68847
- role: "tool",
68848
- content: pendingTools.map((t) => ({
68849
- type: "tool-result",
68850
- toolCallId: t.toolCallId,
68851
- toolName: t.toolName ?? "unknown",
68852
- output: {
68853
- type: "text",
68854
- value: "Cancelled by user."
68855
- }
68856
- }))
68857
- }
68858
- ];
68859
- }
68860
- writeFileSync(join3(activeSession.rootPath, "messages.json"), JSON.stringify(conversationRef.current, null, 2));
68861
- }
68862
- } catch {}
69448
+ conversationRef.current = recoverAbortedTranscript({
69449
+ rootPath: activeSession.rootPath,
69450
+ conversation: conversationRef.current,
69451
+ partialText: displayEvents.getPartialText(),
69452
+ displayMessages: displayMessagesRef.current
69453
+ });
68863
69454
  }
68864
69455
  setMessages((prev) => {
68865
69456
  const updated = prev.map((m2) => isToolMessage(m2) && (m2.status === "pending" || m2.status === "streaming") ? { ...m2, status: "error", result: "Cancelled by user" } : m2);
@@ -68872,51 +69463,30 @@ ${planContent}` : "No plan exists yet. Switch to plan mode (Shift+Tab) to create
68872
69463
  }
68873
69464
  ];
68874
69465
  });
68875
- }, [setThinking, setIsExecuting, subagentStore.setState]);
68876
- const resumeWithQuestionResult = useCallback20((result) => {
69466
+ }, [setThinking, setIsExecuting, subagentStore.setState, displayEvents]);
69467
+ const resumeWithQuestionResult = useCallback22((result) => {
68877
69468
  const toolCallId = pendingToolCallIdRef.current;
68878
69469
  if (!toolCallId)
68879
69470
  return;
68880
- const wrappedOutput = {
68881
- type: "json",
68882
- value: result
68883
- };
68884
- const updated = conversationRef.current.map((msg) => {
68885
- if (msg.role !== "tool")
68886
- return msg;
68887
- if (!Array.isArray(msg.content))
68888
- return msg;
68889
- let mutated = false;
68890
- const nextContent = msg.content.map((part) => {
68891
- if (part && typeof part === "object" && "type" in part && part.type === "tool-result" && part.toolCallId === toolCallId) {
68892
- mutated = true;
68893
- return {
68894
- ...part,
68895
- output: wrappedOutput
68896
- };
68897
- }
68898
- return part;
68899
- });
68900
- return mutated ? { ...msg, content: nextContent } : msg;
68901
- });
69471
+ const updated = rewriteToolResultOutput(conversationRef.current, toolCallId, result);
68902
69472
  conversationRef.current = updated;
68903
69473
  const activeSession = sessionRef.current;
68904
69474
  if (activeSession) {
68905
69475
  try {
68906
- writeFileSync(join3(activeSession.rootPath, "messages.json"), JSON.stringify(updated, null, 2));
69476
+ writeFileSync2(join4(activeSession.rootPath, "messages.json"), JSON.stringify(updated, null, 2));
68907
69477
  } catch {}
68908
69478
  }
68909
69479
  pendingToolCallIdRef.current = null;
68910
69480
  setPendingQuestions(null);
68911
69481
  runAgentRef.current(null);
68912
69482
  }, []);
68913
- const handleQuestionsSubmit = useCallback20((answers) => {
69483
+ const handleQuestionsSubmit = useCallback22((answers) => {
68914
69484
  resumeWithQuestionResult({ answers, skipped: false });
68915
69485
  }, [resumeWithQuestionResult]);
68916
- const handleQuestionsSkip = useCallback20(() => {
69486
+ const handleQuestionsSkip = useCallback22(() => {
68917
69487
  resumeWithQuestionResult({ answers: [], skipped: true });
68918
69488
  }, [resumeWithQuestionResult]);
68919
- const transitionToMode = useCallback20((next) => {
69489
+ const transitionToMode = useCallback22((next) => {
68920
69490
  approvalGateRef.current.updateConfig({
68921
69491
  requireApproval: next === "manual"
68922
69492
  });
@@ -68931,7 +69501,7 @@ ${planContent}` : "No plan exists yet. Switch to plan mode (Shift+Tab) to create
68931
69501
  sessions.updateOperatorSettings(sid, { initialMode: next }).catch((e) => console.error("[operator] Failed to persist mode:", e));
68932
69502
  }
68933
69503
  }, []);
68934
- const cycleMode = useCallback20(() => {
69504
+ const cycleMode = useCallback22(() => {
68935
69505
  const current = operatorModeRef.current;
68936
69506
  const idx = OPERATOR_MODE_CYCLE.indexOf(current);
68937
69507
  const next = OPERATOR_MODE_CYCLE[(idx + 1) % OPERATOR_MODE_CYCLE.length];
@@ -68972,11 +69542,8 @@ ${planContent}` : "No plan exists yet. Switch to plan mode (Shift+Tab) to create
68972
69542
  const removeIdx = selectedQueueIndex;
68973
69543
  setQueuedMessages((prev) => prev.filter((_2, i) => i !== removeIdx));
68974
69544
  setSelectedQueueIndex(-1);
68975
- flushCommandOutput();
68976
- if (cmdFlushTimerRef.current) {
68977
- clearInterval(cmdFlushTimerRef.current);
68978
- cmdFlushTimerRef.current = null;
68979
- }
69545
+ displayEvents.flushCommandOutput();
69546
+ displayEvents.stopCommandOutputFlush();
68980
69547
  setMessages((prev) => prev.map((m2) => isToolMessage(m2) && m2.status === "pending" ? { ...m2, status: "error", result: "Interrupted" } : m2));
68981
69548
  runAgentRef.current(msg);
68982
69549
  return;
@@ -69021,34 +69588,15 @@ ${planContent}` : "No plan exists yet. Switch to plan mode (Shift+Tab) to create
69021
69588
  key.preventDefault?.();
69022
69589
  const toolCallId = pendingToolCallIdRef.current;
69023
69590
  if (toolCallId) {
69024
- const abortedResult = {
69025
- type: "json",
69026
- value: {
69027
- answers: [],
69028
- skipped: true,
69029
- aborted: true
69030
- }
69031
- };
69032
- conversationRef.current = conversationRef.current.map((msg) => {
69033
- if (msg.role !== "tool" || !Array.isArray(msg.content))
69034
- return msg;
69035
- let mutated = false;
69036
- const nextContent = msg.content.map((part) => {
69037
- if (part && typeof part === "object" && "type" in part && part.type === "tool-result" && part.toolCallId === toolCallId) {
69038
- mutated = true;
69039
- return {
69040
- ...part,
69041
- output: abortedResult
69042
- };
69043
- }
69044
- return part;
69045
- });
69046
- return mutated ? { ...msg, content: nextContent } : msg;
69591
+ conversationRef.current = rewriteToolResultOutput(conversationRef.current, toolCallId, {
69592
+ answers: [],
69593
+ skipped: true,
69594
+ aborted: true
69047
69595
  });
69048
69596
  const activeSession = sessionRef.current;
69049
69597
  if (activeSession) {
69050
69598
  try {
69051
- writeFileSync(join3(activeSession.rootPath, "messages.json"), JSON.stringify(conversationRef.current, null, 2));
69599
+ writeFileSync2(join4(activeSession.rootPath, "messages.json"), JSON.stringify(conversationRef.current, null, 2));
69052
69600
  } catch {}
69053
69601
  }
69054
69602
  }
@@ -69197,7 +69745,7 @@ ${planContent}` : "No plan exists yet. Switch to plan mode (Shift+Tab) to create
69197
69745
  fg: colors2.warning,
69198
69746
  children: "STRIKE"
69199
69747
  }, undefined, false, undefined, this),
69200
- (session?.targets[0] || initialConfig?.target) && /* @__PURE__ */ jsxDEV56(Fragment6, {
69748
+ (session?.targets[0] || initialConfig?.target) && /* @__PURE__ */ jsxDEV56(Fragment7, {
69201
69749
  children: [
69202
69750
  /* @__PURE__ */ jsxDEV56("text", {
69203
69751
  fg: colors2.textMuted,
@@ -69240,7 +69788,7 @@ ${planContent}` : "No plan exists yet. Switch to plan mode (Shift+Tab) to create
69240
69788
  questions: pendingQuestions,
69241
69789
  onSubmit: handleQuestionsSubmit,
69242
69790
  onSkip: handleQuestionsSkip
69243
- }, undefined, false, undefined, this) : /* @__PURE__ */ jsxDEV56(Fragment6, {
69791
+ }, undefined, false, undefined, this) : /* @__PURE__ */ jsxDEV56(Fragment7, {
69244
69792
  children: [
69245
69793
  /* @__PURE__ */ jsxDEV56(QueuedMessages, {
69246
69794
  messages: queuedMessages,
@@ -69368,19 +69916,19 @@ function ResponsibleUseDisclosure({
69368
69916
 
69369
69917
  // src/tui/components/switch.tsx
69370
69918
  import React2 from "react";
69371
- import { jsxDEV as jsxDEV58, Fragment as Fragment7 } from "@opentui/react/jsx-dev-runtime";
69919
+ import { jsxDEV as jsxDEV58, Fragment as Fragment8 } from "@opentui/react/jsx-dev-runtime";
69372
69920
  var CaseSymbol = Symbol("Switch.Case");
69373
69921
  var DefaultSymbol = Symbol("Switch.Default");
69374
69922
  function CaseComponent({
69375
69923
  children
69376
69924
  }) {
69377
- return /* @__PURE__ */ jsxDEV58(Fragment7, {
69925
+ return /* @__PURE__ */ jsxDEV58(Fragment8, {
69378
69926
  children
69379
69927
  }, undefined, false, undefined, this);
69380
69928
  }
69381
69929
  CaseComponent[CaseSymbol] = true;
69382
69930
  function DefaultComponent({ children }) {
69383
- return /* @__PURE__ */ jsxDEV58(Fragment7, {
69931
+ return /* @__PURE__ */ jsxDEV58(Fragment8, {
69384
69932
  children
69385
69933
  }, undefined, false, undefined, this);
69386
69934
  }
@@ -69403,7 +69951,7 @@ function SwitchComponent({
69403
69951
  }
69404
69952
  }
69405
69953
  });
69406
- return /* @__PURE__ */ jsxDEV58(Fragment7, {
69954
+ return /* @__PURE__ */ jsxDEV58(Fragment8, {
69407
69955
  children: matchedChild || defaultChild
69408
69956
  }, undefined, false, undefined, this);
69409
69957
  }
@@ -69616,6 +70164,7 @@ function createKeybindings(deps) {
69616
70164
  setExternalDialogOpen,
69617
70165
  setFocusIndex,
69618
70166
  navigableItems,
70167
+ onExit,
69619
70168
  setShowToolsPanel
69620
70169
  } = deps;
69621
70170
  const route = useRoute();
@@ -69631,9 +70180,7 @@ function createKeybindings(deps) {
69631
70180
  const now = Date.now();
69632
70181
  const lastPress = ctrlCPressTime;
69633
70182
  if (lastPress && now - lastPress < 1000) {
69634
- cleanupTerminalFocusMode();
69635
- renderer.destroy();
69636
- process.exit(0);
70183
+ await onExit();
69637
70184
  } else {
69638
70185
  setInputKey((prev) => prev + 1);
69639
70186
  setCtrlCPressTime(now);
@@ -69829,7 +70376,7 @@ import { jsxDEV as jsxDEV61 } from "@opentui/react/jsx-dev-runtime";
69829
70376
  installObfuscationTextPatch();
69830
70377
  extend({ frameBuffer: FrameBufferRenderable });
69831
70378
  extend({ frameBuffer: FrameBufferRenderable });
69832
- function App({ appConfig }) {
70379
+ function App({ appConfig, onExit }) {
69833
70380
  const [focusIndex, setFocusIndex] = useState40(0);
69834
70381
  const [cwd, setCwd] = useState40(process.cwd());
69835
70382
  const [ctrlCPressTime, setCtrlCPressTime] = useState40(null);
@@ -69860,6 +70407,7 @@ function App({ appConfig }) {
69860
70407
  children: /* @__PURE__ */ jsxDEV61(DialogProvider, {
69861
70408
  children: /* @__PURE__ */ jsxDEV61(AgentProvider, {
69862
70409
  children: /* @__PURE__ */ jsxDEV61(CommandProvider, {
70410
+ onExit,
69863
70411
  onOpenSessionsDialog: () => setShowSessionsDialog(true),
69864
70412
  onOpenThemeDialog: () => setShowThemeDialog(true),
69865
70413
  onOpenAdvancedDialog: () => setShowAdvancedDialog(true),
@@ -69885,7 +70433,8 @@ function App({ appConfig }) {
69885
70433
  setShowSessionsDialog,
69886
70434
  setShowShortcutsDialog,
69887
70435
  setFocusIndex,
69888
- navigableItems
70436
+ navigableItems,
70437
+ onExit
69889
70438
  },
69890
70439
  children: /* @__PURE__ */ jsxDEV61(AppContent, {
69891
70440
  focusIndex,
@@ -69982,7 +70531,7 @@ function AppContent({
69982
70531
  toast(`Update available: v${currentVersion} → v${latestVersion}. Run: pensar upgrade`, "warn", 8000);
69983
70532
  });
69984
70533
  const checkAuthToken = async () => {
69985
- const { isTokenExpired, isConnected: isConnected2 } = await import("./index-4ad3sdwk.js");
70534
+ const { isTokenExpired, isConnected: isConnected2 } = await import("./index-4v5jc2td.js");
69986
70535
  if (isConnected2(config2.data) && config2.data.accessToken && isTokenExpired(config2.data.accessToken, 60) && !config2.data.refreshToken && !config2.data.pensarAPIKey) {
69987
70536
  toast("Your Pensar Console session has expired. Run /login to refresh.", "warn", 8000);
69988
70537
  }
@@ -70317,6 +70866,7 @@ async function main() {
70317
70866
  const transparent = appConfig.transparentBackground ?? false;
70318
70867
  const themeColors = resolveThemeColors(getTheme(themeName), mode, transparent);
70319
70868
  overlayThemeRef.current = themeColors;
70869
+ const observabilityRuntime = startObservabilityRuntime();
70320
70870
  const renderer = await createCliRenderer({
70321
70871
  exitOnCtrlC: false,
70322
70872
  consoleOptions: buildConsoleOptions(themeColors),
@@ -70324,27 +70874,19 @@ async function main() {
70324
70874
  });
70325
70875
  const { copyToClipboard } = createClipboardManager(renderer);
70326
70876
  setupAutoCopy(renderer, copyToClipboard);
70327
- const cleanup = () => {
70328
- cleanupTerminalFocusMode();
70329
- renderer.destroy();
70330
- process.exit(0);
70331
- };
70332
- process.on("SIGINT", cleanup);
70333
- process.on("SIGTERM", cleanup);
70334
- process.on("uncaughtException", (err) => {
70335
- cleanupTerminalFocusMode();
70336
- renderer.destroy();
70337
- console.error("Uncaught exception:", err);
70338
- writeErrorLog(err, "UNCAUGHT");
70339
- process.exit(1);
70340
- });
70341
- process.on("unhandledRejection", (reason) => {
70342
- cleanupTerminalFocusMode();
70343
- renderer.destroy();
70344
- console.error("Unhandled rejection:", reason);
70345
- writeErrorLog(reason, "UNHANDLED_REJECTION");
70346
- process.exit(1);
70877
+ const exitWith = installObservabilityExitHandlers(observabilityRuntime, {
70878
+ cleanup: () => {
70879
+ cleanupTerminalFocusMode();
70880
+ renderer.destroy();
70881
+ },
70882
+ onError: (error, source) => {
70883
+ const uncaught = source === "uncaughtException";
70884
+ const label = uncaught ? "Uncaught exception:" : "Unhandled rejection:";
70885
+ console.error(label, error);
70886
+ writeErrorLog(error, uncaught ? "UNCAUGHT" : "UNHANDLED_REJECTION");
70887
+ }
70347
70888
  });
70889
+ const cleanup = () => exitWith(0);
70348
70890
  const obfuscateEnabled = process.env.PENSAR_OBFUSCATE === "1";
70349
70891
  createRoot(renderer).render(/* @__PURE__ */ jsxDEV61(ObfuscationProvider, {
70350
70892
  initialEnabled: obfuscateEnabled,
@@ -70359,7 +70901,8 @@ async function main() {
70359
70901
  children: [
70360
70902
  /* @__PURE__ */ jsxDEV61(ErrorBoundary, {
70361
70903
  children: /* @__PURE__ */ jsxDEV61(App, {
70362
- appConfig
70904
+ appConfig,
70905
+ onExit: cleanup
70363
70906
  }, undefined, false, undefined, this)
70364
70907
  }, undefined, false, undefined, this),
70365
70908
  /* @__PURE__ */ jsxDEV61(ToastContainer, {}, undefined, false, undefined, this)