@bike4mind/cli 0.2.31-cli-update-command.19462 → 0.2.31-cli-update-command.19497

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,14 +1,14 @@
1
1
  #!/usr/bin/env node
2
2
  import "./chunk-GQGOWACU.js";
3
- import "./chunk-DEW32L4X.js";
4
- import "./chunk-U4HDDXWT.js";
3
+ import "./chunk-2LLA4MTN.js";
4
+ import "./chunk-ZOWCX4MQ.js";
5
5
  import "./chunk-BPFEGDC7.js";
6
6
  import "./chunk-BDQBOLYG.js";
7
7
  import {
8
8
  getEffectiveApiKey,
9
9
  getOpenWeatherKey,
10
10
  getSerperKey
11
- } from "./chunk-6HWTNX47.js";
11
+ } from "./chunk-T67NGQW6.js";
12
12
  import {
13
13
  ConfigStore,
14
14
  logger
@@ -16,7 +16,7 @@ import {
16
16
  import {
17
17
  checkForUpdate,
18
18
  package_default
19
- } from "./chunk-FZXNUST6.js";
19
+ } from "./chunk-IXIOYIPO.js";
20
20
  import {
21
21
  selectActiveBackgroundAgents,
22
22
  useCliStore
@@ -32,7 +32,7 @@ import {
32
32
  OpenAIBackend,
33
33
  OpenAIImageService,
34
34
  XAIImageService
35
- } from "./chunk-24JZFYBV.js";
35
+ } from "./chunk-RI45VJW3.js";
36
36
  import {
37
37
  AiEvents,
38
38
  ApiKeyEvents,
@@ -90,7 +90,7 @@ import {
90
90
  getMcpProviderMetadata,
91
91
  getViewById,
92
92
  resolveNavigationIntents
93
- } from "./chunk-E77VWEKZ.js";
93
+ } from "./chunk-GE7Q64MS.js";
94
94
  import {
95
95
  Logger
96
96
  } from "./chunk-OCYRD7D6.js";
@@ -100,7 +100,7 @@ import React21, { useState as useState10, useEffect as useEffect7, useCallback a
100
100
  import { render, Box as Box20, Text as Text20, useApp, useInput as useInput9 } from "ink";
101
101
  import { execSync } from "child_process";
102
102
  import { randomBytes as randomBytes5 } from "crypto";
103
- import { v4 as uuidv411 } from "uuid";
103
+ import { v4 as uuidv413 } from "uuid";
104
104
 
105
105
  // src/components/App.tsx
106
106
  import React15, { useState as useState6, useEffect as useEffect5 } from "react";
@@ -10186,7 +10186,9 @@ var SolverIdSchema = z139.enum([
10186
10186
  "tabu",
10187
10187
  "genetic-algorithm",
10188
10188
  "ant-colony",
10189
- "highs"
10189
+ "highs",
10190
+ "simulated-qaoa",
10191
+ "ionq-qaoa"
10190
10192
  ]);
10191
10193
 
10192
10194
  // ../../b4m-core/packages/quantum/dist/src/solvers/greedy-solver.js
@@ -10837,6 +10839,396 @@ function constructSolution(allOps, pheromone, heuristic, rng) {
10837
10839
  return result;
10838
10840
  }
10839
10841
 
10842
+ // ../../b4m-core/packages/quantum/dist/src/solvers/metadata.js
10843
+ var solverMetadata = {
10844
+ naive: {
10845
+ icon: "\u{1F40C}",
10846
+ // 🐌
10847
+ color: "neutral",
10848
+ complexity: "Exponential - O(n!)",
10849
+ requires: "Browser compute",
10850
+ tagline: "Try every possible solution",
10851
+ fullDescription: "The simplest possible approach: systematically enumerate and evaluate every valid permutation of operations. Guaranteed to find the optimal solution, but becomes computationally infeasible for larger problems due to factorial growth.",
10852
+ howItWorks: [
10853
+ "Generate all valid operation orderings (permutations)",
10854
+ "Evaluate each ordering to compute makespan",
10855
+ "Track the best solution found",
10856
+ "Return the globally optimal solution"
10857
+ ],
10858
+ keyParameters: [{ name: "Search Space", value: "n! permutations for n operations (with pruning for precedence)" }],
10859
+ strengths: ["Guarantees optimal solution", "Simple to understand and implement", "No parameter tuning needed"],
10860
+ weaknesses: [
10861
+ "O(n!) time complexity",
10862
+ "Infeasible for problems with >10 operations",
10863
+ "No early termination heuristics"
10864
+ ],
10865
+ bestFor: "Tiny problems (\u226410 ops) where you need a guaranteed optimal baseline",
10866
+ wikipedia: "https://en.wikipedia.org/wiki/Brute-force_search"
10867
+ },
10868
+ greedy: {
10869
+ icon: "\u26A1",
10870
+ // ⚡
10871
+ color: "success",
10872
+ complexity: "O(n\xB2)",
10873
+ requires: "Browser compute",
10874
+ tagline: "Always pick the locally best choice",
10875
+ fullDescription: 'A fast heuristic that builds a solution by always selecting the "best" available operation at each step. Uses the Shortest Processing Time (SPT) rule: schedule the shortest available operation first. Fast but may miss global optimum.',
10876
+ howItWorks: [
10877
+ "Start with an empty schedule",
10878
+ "Find all operations whose predecessors are complete",
10879
+ "Pick the one with shortest duration (SPT rule)",
10880
+ "Schedule it at the earliest possible time",
10881
+ "Repeat until all operations scheduled"
10882
+ ],
10883
+ keyParameters: [{ name: "Priority Rule", value: "SPT (Shortest Processing Time) \u2014 favors quick operations" }],
10884
+ strengths: ["Extremely fast (O(n\xB2))", "Produces valid solutions instantly", "Good baseline for comparison"],
10885
+ weaknesses: ["No backtracking", "Often far from optimal", "Can make locally optimal but globally poor choices"],
10886
+ bestFor: "Quick baseline, real-time scheduling, or as initial solution for metaheuristics",
10887
+ wikipedia: "https://en.wikipedia.org/wiki/Greedy_algorithm"
10888
+ },
10889
+ "random-restart": {
10890
+ icon: "\u{1F3B2}",
10891
+ // 🎲
10892
+ color: "primary",
10893
+ complexity: "~5 seconds",
10894
+ requires: "Browser compute",
10895
+ tagline: "Climb hills, restart when stuck",
10896
+ fullDescription: "Combines local search with random restarts. From each starting point, repeatedly move to better neighboring solutions until no improvement is possible (local optimum). Then restart from a new random solution. The best solution across all restarts is returned.",
10897
+ howItWorks: [
10898
+ "Generate a random valid schedule",
10899
+ "Try swapping pairs of operations",
10900
+ "Accept swaps that improve makespan",
10901
+ "When stuck (no improving swaps), restart from new random solution",
10902
+ "Track best solution across all restarts"
10903
+ ],
10904
+ keyParameters: [
10905
+ { name: "Max Restarts", value: "Number of times to restart from random (100)" },
10906
+ { name: "Stagnation Limit", value: "Iterations without improvement before restart (1000)" }
10907
+ ],
10908
+ strengths: ["Escapes local optima via restarts", "Simple and parallelizable", "Better than pure hill climbing"],
10909
+ weaknesses: [
10910
+ "No information sharing between restarts",
10911
+ "May revisit similar solutions",
10912
+ "Random restarts are uninformed"
10913
+ ],
10914
+ bestFor: "Medium problems where pure hill climbing gets stuck",
10915
+ wikipedia: "https://en.wikipedia.org/wiki/Hill_climbing#Random-restart_hill_climbing"
10916
+ },
10917
+ "simulated-annealing": {
10918
+ icon: "\u{1F525}",
10919
+ // 🔥
10920
+ color: "warning",
10921
+ complexity: "~7 seconds",
10922
+ requires: "Browser compute",
10923
+ tagline: "Cool down from chaos to order",
10924
+ fullDescription: 'Inspired by metallurgy: heating metal and slowly cooling it produces stronger crystal structures. SA starts "hot" (accepting bad moves) and "cools" (becoming pickier). This allows escaping local optima early while converging to good solutions later.',
10925
+ howItWorks: [
10926
+ "Start with high temperature (T=100)",
10927
+ "Generate neighbor by swapping two operations",
10928
+ "If better, always accept",
10929
+ "If worse, accept with probability e^(-\u0394/T)",
10930
+ "Gradually reduce temperature (cooling)",
10931
+ "As T\u21920, behaves like pure hill climbing"
10932
+ ],
10933
+ keyParameters: [
10934
+ { name: "Initial Temperature", value: 'Starting "heat" level (100)' },
10935
+ { name: "Cooling Rate", value: "How fast temperature decreases (~0.9999...)" },
10936
+ { name: "Iterations", value: "5 million moves evaluated" }
10937
+ ],
10938
+ strengths: [
10939
+ "Escapes local optima probabilistically",
10940
+ "Well-studied theoretical properties",
10941
+ "Single parameter to tune (cooling schedule)"
10942
+ ],
10943
+ weaknesses: ["Cooling schedule is problem-dependent", "Can be slow to converge", "Single-solution method"],
10944
+ bestFor: "General optimization when you have moderate compute budget",
10945
+ wikipedia: "https://en.wikipedia.org/wiki/Simulated_annealing"
10946
+ },
10947
+ "simulated-annealing-medium": {
10948
+ icon: "\u{1F525}",
10949
+ // 🔥
10950
+ color: "warning",
10951
+ complexity: "~70 seconds",
10952
+ requires: "Browser compute",
10953
+ tagline: "Extended cooling for deeper exploration",
10954
+ fullDescription: "Same algorithm as SA but with 10\xD7 more iterations (50M). The slower cooling schedule allows more thorough exploration of the solution space before settling into a final basin.",
10955
+ howItWorks: ["Same as SA standard", "But with 50 million iterations", "Slower cooling = more exploration time"],
10956
+ keyParameters: [
10957
+ { name: "Iterations", value: "50 million moves (10\xD7 standard)" },
10958
+ { name: "Timeout", value: "5 minutes max" }
10959
+ ],
10960
+ strengths: ["More thorough than standard SA", "Better for complex landscapes", "Higher chance of global optimum"],
10961
+ weaknesses: [
10962
+ "10\xD7 slower than standard SA",
10963
+ "Diminishing returns on easy problems",
10964
+ "May be overkill for small instances"
10965
+ ],
10966
+ bestFor: "When standard SA finds good but not great solutions",
10967
+ wikipedia: "https://en.wikipedia.org/wiki/Simulated_annealing"
10968
+ },
10969
+ "simulated-annealing-large": {
10970
+ icon: "\u{1F525}",
10971
+ // 🔥
10972
+ color: "danger",
10973
+ complexity: "~12 minutes",
10974
+ requires: "Browser compute",
10975
+ tagline: "Maximum exploration budget",
10976
+ fullDescription: "The heavy artillery: 500 million iterations with up to 30 minutes of compute. For when you absolutely need the best possible solution and have time to wait.",
10977
+ howItWorks: [
10978
+ "Same as SA standard/medium",
10979
+ "But with 500 million iterations",
10980
+ "Very slow cooling for maximum exploration"
10981
+ ],
10982
+ keyParameters: [
10983
+ { name: "Iterations", value: "500 million moves (100\xD7 standard)" },
10984
+ { name: "Timeout", value: "30 minutes max" }
10985
+ ],
10986
+ strengths: ["Maximum exploration", "Best chance at global optimum", "Leaves no stone unturned"],
10987
+ weaknesses: ["Very slow (up to 30 min)", "Massive overkill for small problems", "Diminishing returns"],
10988
+ bestFor: "Large problems where quality matters more than time",
10989
+ wikipedia: "https://en.wikipedia.org/wiki/Simulated_annealing"
10990
+ },
10991
+ tabu: {
10992
+ icon: "\u{1F50D}",
10993
+ // 🔍
10994
+ color: "primary",
10995
+ complexity: "~20\u201340 seconds",
10996
+ requires: "Browser compute",
10997
+ tagline: "Remember mistakes to avoid repeating them",
10998
+ fullDescription: 'This is a classical (simple) Tabu Search following Glover (1986) with aspiration by objective. It maintains a short-term recency-based memory of recent swap moves that are temporarily forbidden ("tabu"). The neighborhood is generated by random pairwise swaps of operations in the permutation representation. Unlike reactive or robust variants, this implementation uses a fixed tabu tenure and does not employ long-term memory, intensification, or diversification phases.',
10999
+ howItWorks: [
11000
+ "Start from a random permutation of operations (seed: 42 for reproducibility)",
11001
+ "Generate 20 random neighbor solutions by swapping two operation positions",
11002
+ "Select the best non-tabu neighbor (even if worse than current \u2014 forced exploration)",
11003
+ "Record the swap as tabu for 15 iterations (fixed tenure)",
11004
+ "Aspiration criterion: override tabu if move produces a new global best",
11005
+ "Repeat for 10,000 iterations or until timeout (15s)",
11006
+ "Periodically clean expired entries from the tabu list"
11007
+ ],
11008
+ keyParameters: [
11009
+ { name: "Variant", value: "Simple Tabu Search (Glover 1986) with aspiration by objective" },
11010
+ { name: "Tabu Structure", value: "Swap-based \u2014 records position pairs (i,j) that were swapped" },
11011
+ { name: "Tabu Tenure", value: "Fixed at 15 iterations (no reactive adjustment)" },
11012
+ { name: "Neighborhood Size", value: "20 random swap candidates evaluated per iteration" },
11013
+ { name: "Total Iterations", value: "10,000" },
11014
+ { name: "Aspiration", value: "Accept tabu move if it improves the global best" },
11015
+ { name: "Memory Type", value: "Short-term recency only (no frequency-based long-term memory)" }
11016
+ ],
11017
+ strengths: [
11018
+ "Memory prevents cycling back to recently visited solutions",
11019
+ "Forced acceptance of worse moves enables escaping local optima",
11020
+ "Aspiration criterion preserves exploitation of exceptional solutions",
11021
+ "Deterministic with seed (reproducible results)"
11022
+ ],
11023
+ weaknesses: [
11024
+ "Fixed tenure \u2014 no adaptive/reactive adjustment to search dynamics",
11025
+ "No long-term memory (frequency-based diversification)",
11026
+ "No intensification phase to deep-search promising regions",
11027
+ "Small neighborhood (20) may miss good moves in large search spaces"
11028
+ ],
11029
+ bestFor: "Medium-complexity problems with many local optima and plateau regions",
11030
+ wikipedia: "https://en.wikipedia.org/wiki/Tabu_search",
11031
+ otherResources: [
11032
+ { label: "Glover (1986) \u2014 Original paper", url: "https://doi.org/10.1016/0305-0548(86)90048-1" },
11033
+ {
11034
+ label: "Glover & Laguna \u2014 Tabu Search (book)",
11035
+ url: "https://en.wikipedia.org/wiki/Tabu_search#References"
11036
+ }
11037
+ ]
11038
+ },
11039
+ "genetic-algorithm": {
11040
+ icon: "\u{1F9EC}",
11041
+ // 🧬
11042
+ color: "success",
11043
+ complexity: "~30 seconds",
11044
+ requires: "Browser compute",
11045
+ tagline: "Evolve solutions through natural selection",
11046
+ fullDescription: 'Inspired by biological evolution: maintain a population of solutions that reproduce, mutate, and compete. Better solutions are more likely to survive and pass on their "genes" (good partial solutions). Over generations, the population evolves toward optimality.',
11047
+ howItWorks: [
11048
+ "Initialize population of 100 random schedules",
11049
+ "Evaluate fitness (1/makespan) for each",
11050
+ "Selection: pick parents via tournament (best of 5 random)",
11051
+ "Crossover: combine two parents to create child",
11052
+ "Mutation: randomly swap operations (20% chance)",
11053
+ "Elitism: top 5 solutions survive unchanged",
11054
+ "Repeat for 15,000 generations"
11055
+ ],
11056
+ keyParameters: [
11057
+ { name: "Population Size", value: "Number of solutions maintained (100)" },
11058
+ { name: "Generations", value: "Evolution cycles (15,000)" },
11059
+ { name: "Crossover Rate", value: "Probability of combining parents (80%)" },
11060
+ { name: "Mutation Rate", value: "Probability of random change (20%)" },
11061
+ { name: "Elite Count", value: "Best solutions preserved unchanged (5)" }
11062
+ ],
11063
+ strengths: ["Population maintains diversity", "Crossover combines good building blocks", "Naturally parallel"],
11064
+ weaknesses: ["Many parameters to tune", "Can converge prematurely", "Crossover design is problem-specific"],
11065
+ bestFor: "Complex problems where good solutions share common substructures",
11066
+ wikipedia: "https://en.wikipedia.org/wiki/Genetic_algorithm",
11067
+ otherResources: [
11068
+ {
11069
+ label: "Order Crossover (OX)",
11070
+ url: "https://en.wikipedia.org/wiki/Crossover_(genetic_algorithm)#Order_crossover_(OX)"
11071
+ }
11072
+ ]
11073
+ },
11074
+ "ant-colony": {
11075
+ icon: "\u{1F41C}",
11076
+ // 🐜
11077
+ color: "success",
11078
+ complexity: "~30 seconds",
11079
+ requires: "Browser compute",
11080
+ tagline: "Follow the pheromone trails",
11081
+ fullDescription: "Inspired by how ants find shortest paths to food: they deposit pheromones, and other ants preferentially follow stronger trails. Over time, shorter paths accumulate more pheromone (ants traverse them faster), creating positive feedback toward good solutions.",
11082
+ howItWorks: [
11083
+ "Initialize pheromone trails uniformly",
11084
+ "Each ant builds a complete schedule probabilistically",
11085
+ "Operations with more pheromone are more likely to be chosen",
11086
+ "After all ants finish, evaluate their solutions",
11087
+ "Deposit pheromone on edges used by good solutions",
11088
+ "Evaporate some pheromone (forget old information)",
11089
+ "Best ant's solution gets extra pheromone (elitism)"
11090
+ ],
11091
+ keyParameters: [
11092
+ { name: "Number of Ants", value: "Solutions built per iteration (50)" },
11093
+ { name: "Iterations", value: "Pheromone update cycles (100,000)" },
11094
+ { name: "Alpha (\u03B1)", value: "Pheromone importance (1.0)" },
11095
+ { name: "Beta (\u03B2)", value: "Heuristic importance (2.0)" },
11096
+ { name: "Evaporation Rate", value: "Pheromone decay per iteration (10%)" }
11097
+ ],
11098
+ strengths: ["Implicit parallelism", "Positive feedback accelerates convergence", "Robust to problem changes"],
11099
+ weaknesses: ["Many parameters", "Can converge prematurely", "Pheromone model must match problem"],
11100
+ bestFor: 'Routing and sequencing problems with clear "edge" structure',
11101
+ wikipedia: "https://en.wikipedia.org/wiki/Ant_colony_optimization_algorithms",
11102
+ otherResources: [{ label: "Marco Dorigo (inventor)", url: "https://en.wikipedia.org/wiki/Marco_Dorigo" }]
11103
+ },
11104
+ highs: {
11105
+ icon: "\u{1F3AF}",
11106
+ // 🎯
11107
+ color: "success",
11108
+ complexity: "Seconds to minutes",
11109
+ requires: "Browser compute (WASM)",
11110
+ available: false,
11111
+ tagline: "Open-source solver rivaling commercial giants",
11112
+ fullDescription: "HiGHS (High-performance Interior-point, Gradient-descent, Simplex) is an MIT-licensed open-source solver that achieves 90%+ of commercial solver performance. Developed at the University of Edinburgh, it has rapidly become the leading open-source alternative to Gurobi and COPT for linear and mixed-integer programming.",
11113
+ howItWorks: [
11114
+ "Formulate problem as Integer Linear Program (ILP/MIP)",
11115
+ "Presolve: simplify model, tighten bounds, detect infeasibility",
11116
+ "LP relaxation using dual simplex or interior-point method",
11117
+ "Branch-and-bound with sophisticated node selection",
11118
+ "Cutting planes: Gomory cuts, MIR cuts, cover cuts",
11119
+ "Return optimal (or near-optimal) solution with gap certificate"
11120
+ ],
11121
+ keyParameters: [
11122
+ { name: "Time Limit", value: "Maximum solve time before returning best found" },
11123
+ { name: "MIP Gap", value: "Stop when proven within X% of optimal (e.g., 0.01 = 1%)" },
11124
+ { name: "Threads", value: "Number of parallel threads (default: all cores)" }
11125
+ ],
11126
+ strengths: [
11127
+ "MIT licensed \u2014 fully open source, embed anywhere",
11128
+ "90%+ of Gurobi/COPT performance on most problems",
11129
+ "Active development with rapid improvements",
11130
+ "Can deploy in customer infrastructure (no license servers)"
11131
+ ],
11132
+ weaknesses: [
11133
+ "Slightly slower than Gurobi on very large instances",
11134
+ "Fewer specialized cuts than commercial solvers",
11135
+ "Less mature ecosystem (but growing fast)"
11136
+ ],
11137
+ bestFor: "Production scheduling when you need commercial-grade results without commercial licensing",
11138
+ wikipedia: "https://en.wikipedia.org/wiki/HiGHS_optimization_solver",
11139
+ otherResources: [
11140
+ { label: "HiGHS GitHub", url: "https://github.com/ERGO-Code/HiGHS" },
11141
+ { label: "HiGHS Documentation", url: "https://highs.dev/" },
11142
+ { label: "Mittelmann MIP Benchmarks", url: "http://plato.asu.edu/ftp/milp.html" }
11143
+ ]
11144
+ },
11145
+ "simulated-qaoa": {
11146
+ icon: "\u{1F52E}",
11147
+ // 🔮
11148
+ color: "primary",
11149
+ complexity: "~30\u201360 seconds",
11150
+ requires: "Backend compute",
11151
+ available: false,
11152
+ tagline: "Quantum-inspired classical simulation",
11153
+ fullDescription: "A classical simulator of the Quantum Approximate Optimization Algorithm (QAOA). Encodes the job-shop scheduling problem as a QUBO (Quadratic Unconstrained Binary Optimization), then simulates the quantum variational circuit classically. Provides a preview of quantum advantage without requiring real quantum hardware.",
11154
+ howItWorks: [
11155
+ "Encode scheduling problem as QUBO matrix",
11156
+ "Initialize simulated quantum state |+\u27E9\u2297\u207F",
11157
+ "Apply p layers of QAOA circuit (problem + mixer unitaries)",
11158
+ "Classically optimize variational parameters (\u03B3, \u03B2)",
11159
+ "Measure (sample) the final state to extract candidate schedules",
11160
+ "Decode best bitstring back to a valid schedule"
11161
+ ],
11162
+ keyParameters: [
11163
+ { name: "QAOA Depth (p)", value: "Number of alternating layers (default: 3)" },
11164
+ { name: "Optimizer", value: "COBYLA for variational parameter optimization" },
11165
+ { name: "Shots", value: "Number of measurement samples (1024)" },
11166
+ { name: "Qubits", value: "One per possible (job, timeslot) assignment" }
11167
+ ],
11168
+ strengths: [
11169
+ "Preview quantum algorithms without quantum hardware",
11170
+ "Explores solution space via quantum superposition (simulated)",
11171
+ "Useful for benchmarking against real quantum results",
11172
+ "Runs entirely in-browser or on backend \u2014 no API keys needed"
11173
+ ],
11174
+ weaknesses: [
11175
+ "Exponential classical overhead \u2014 limited to ~20 qubits",
11176
+ "Cannot capture true quantum speedup",
11177
+ "QUBO encoding may not be optimal for all problem structures",
11178
+ "Variational optimization can get stuck in local minima"
11179
+ ],
11180
+ bestFor: "Small problems where you want to preview quantum approaches before investing in real hardware",
11181
+ wikipedia: "https://en.wikipedia.org/wiki/Quantum_approximate_optimization_algorithm",
11182
+ otherResources: [
11183
+ { label: "Farhi et al. (2014) \u2014 Original QAOA Paper", url: "https://arxiv.org/abs/1411.4028" },
11184
+ { label: "QUBO Formulation Guide", url: "https://en.wikipedia.org/wiki/Quadratic_unconstrained_binary_optimization" }
11185
+ ]
11186
+ },
11187
+ "ionq-qaoa": {
11188
+ icon: "\u269B\uFE0F",
11189
+ // ⚛️
11190
+ color: "danger",
11191
+ complexity: "Minutes (queue + execution)",
11192
+ requires: "IonQ API key + credits",
11193
+ available: false,
11194
+ tagline: "Real quantum hardware optimization",
11195
+ fullDescription: "Runs the Quantum Approximate Optimization Algorithm on IonQ\u2019s trapped-ion quantum computers. This is real quantum computation \u2014 the problem is encoded as a QUBO, compiled to native quantum gates, and executed on physical qubits. Results include genuine quantum effects like superposition and entanglement.",
11196
+ howItWorks: [
11197
+ "Encode scheduling problem as QUBO matrix",
11198
+ "Compile QAOA circuit to IonQ\u2019s native gate set (MS, GPi, GPi2)",
11199
+ "Submit job to IonQ cloud via API",
11200
+ "Queue for hardware execution (trapped-ion processor)",
11201
+ "Execute quantum circuit with real qubits",
11202
+ "Retrieve measurement results and decode to schedule"
11203
+ ],
11204
+ keyParameters: [
11205
+ { name: "QAOA Depth (p)", value: "Number of alternating layers (default: 1\u20132 for NISQ)" },
11206
+ { name: "Backend", value: "IonQ Harmony (11 qubits) or Aria (25 qubits)" },
11207
+ { name: "Shots", value: "Number of circuit executions (1024)" },
11208
+ { name: "Error Mitigation", value: "IonQ debiasing enabled by default" }
11209
+ ],
11210
+ strengths: [
11211
+ "Real quantum computation with genuine quantum effects",
11212
+ "Trapped-ion qubits have high gate fidelity (~99.5%)",
11213
+ "All-to-all connectivity \u2014 no SWAP overhead",
11214
+ "Potential for quantum advantage on certain problem classes"
11215
+ ],
11216
+ weaknesses: [
11217
+ "Requires IonQ API key and credits (not free)",
11218
+ "Queue wait times can be minutes to hours",
11219
+ "Limited to ~25 qubits (current hardware)",
11220
+ "NISQ noise limits circuit depth and solution quality"
11221
+ ],
11222
+ bestFor: "Exploring real quantum optimization on small problem instances when you have IonQ access",
11223
+ wikipedia: "https://en.wikipedia.org/wiki/Trapped-ion_quantum_computer",
11224
+ otherResources: [
11225
+ { label: "IonQ Documentation", url: "https://docs.ionq.com/" },
11226
+ { label: "IonQ Aria Specs", url: "https://ionq.com/quantum-systems/aria" },
11227
+ { label: "QAOA on NISQ Devices", url: "https://arxiv.org/abs/1812.01041" }
11228
+ ]
11229
+ }
11230
+ };
11231
+
10840
11232
  // ../../b4m-core/packages/quantum/dist/src/solvers/index.js
10841
11233
  var allSolvers = [
10842
11234
  greedySolver,
@@ -10856,9 +11248,15 @@ function getSolver(id) {
10856
11248
  function getAvailableSolverIds() {
10857
11249
  return Array.from(solverRegistry.keys());
10858
11250
  }
11251
+ var displaySolvers = [
11252
+ ...allSolvers.map((s) => ({ id: s.id, name: s.name, description: s.description })),
11253
+ { id: "highs", name: "HiGHS (WASM)", description: solverMetadata.highs.tagline },
11254
+ { id: "simulated-qaoa", name: "Simulated QAOA", description: solverMetadata["simulated-qaoa"].tagline },
11255
+ { id: "ionq-qaoa", name: "IonQ QAOA", description: solverMetadata["ionq-qaoa"].tagline }
11256
+ ];
10859
11257
 
10860
11258
  // ../../b4m-core/packages/quantum/dist/src/prompts/system-prompt.js
10861
- var QUANTUM_CANVASSER_SYSTEM_PROMPT = `You are the Optimization Canvasser, an AI agent specializing in combinatorial optimization and job-shop scheduling. You help users formulate scheduling problems, run solver algorithms, and interpret optimization results. You are solver-agnostic \u2014 you route problems to the best solver whether classical (greedy, simulated annealing, genetic algorithms, HiGHS MIP) or quantum (QAOA), building credibility through honest recommendations.
11259
+ var QUANTUM_CANVASSER_SYSTEM_PROMPT = `You are OptiHashi, an AI agent specializing in combinatorial optimization and job-shop scheduling. You help users formulate scheduling problems, run solver algorithms, and interpret optimization results. You are solver-agnostic \u2014 you route problems to the best solver whether classical (greedy, simulated annealing, genetic algorithms, HiGHS MIP) or quantum (QAOA), building credibility through honest recommendations.
10862
11260
 
10863
11261
  ## Your Capabilities
10864
11262
 
@@ -13988,6 +14386,10 @@ var ServerToolExecutor = class {
13988
14386
  };
13989
14387
 
13990
14388
  // src/llm/ToolRouter.ts
14389
+ var wsToolExecutor = null;
14390
+ function setWebSocketToolExecutor(executor) {
14391
+ wsToolExecutor = executor;
14392
+ }
13991
14393
  var SERVER_TOOLS = ["weather_info", "web_search", "web_fetch"];
13992
14394
  var LOCAL_TOOLS = [
13993
14395
  "file_read",
@@ -14010,7 +14412,15 @@ function isLocalTool(toolName) {
14010
14412
  }
14011
14413
  async function executeTool(toolName, input, apiClient, localToolFn) {
14012
14414
  if (isServerTool(toolName)) {
14013
- logger.debug(`[ToolRouter] Routing ${toolName} to server`);
14415
+ if (wsToolExecutor) {
14416
+ logger.debug(`[ToolRouter] Routing ${toolName} to server via WebSocket`);
14417
+ const result = await wsToolExecutor.execute(toolName, input);
14418
+ if (!result.success) {
14419
+ return `Error executing ${toolName}: ${result.error || "Tool execution failed"}`;
14420
+ }
14421
+ return typeof result.content === "string" ? result.content : JSON.stringify(result.content ?? "");
14422
+ }
14423
+ logger.debug(`[ToolRouter] Routing ${toolName} to server via HTTP`);
14014
14424
  const executor = new ServerToolExecutor(apiClient);
14015
14425
  return await executor.executeTool(toolName, input);
14016
14426
  } else if (isLocalTool(toolName)) {
@@ -15972,6 +16382,173 @@ var ServerLlmBackend = class {
15972
16382
  }
15973
16383
  };
15974
16384
 
16385
+ // src/llm/WebSocketLlmBackend.ts
16386
+ import { v4 as uuidv411 } from "uuid";
16387
+ function stripThinkingBlocks2(text) {
16388
+ return text.replace(/<think>[\s\S]*?<\/think>/g, "").trim();
16389
+ }
16390
+ var WebSocketLlmBackend = class {
16391
+ constructor(options) {
16392
+ this.wsManager = options.wsManager;
16393
+ this.apiClient = options.apiClient;
16394
+ this.currentModel = options.model;
16395
+ this.tokenGetter = options.tokenGetter;
16396
+ this.wsCompletionUrl = options.wsCompletionUrl;
16397
+ }
16398
+ /**
16399
+ * Send completion request via HTTP POST, receive streaming response via WebSocket.
16400
+ * Collects all streamed chunks, then calls callback once at completion
16401
+ * with the full accumulated content.
16402
+ */
16403
+ async complete(model, messages, options, callback) {
16404
+ logger.debug(`[WebSocketLlmBackend] Starting complete() with model: ${model}`);
16405
+ if (options.abortSignal?.aborted) {
16406
+ logger.debug("[WebSocketLlmBackend] Request aborted before start");
16407
+ return;
16408
+ }
16409
+ if (!this.wsManager.isConnected) {
16410
+ throw new Error("WebSocket is not connected");
16411
+ }
16412
+ const requestId = uuidv411();
16413
+ return new Promise((resolve3, reject) => {
16414
+ const isVerbose = process.env.B4M_VERBOSE === "1";
16415
+ const isUltraVerbose = process.env.B4M_DEBUG_STREAM === "1";
16416
+ const streamLogger = new StreamLogger(logger, "WebSocketLlmBackend", isVerbose, isUltraVerbose);
16417
+ streamLogger.streamStart();
16418
+ let eventCount = 0;
16419
+ let accumulatedText = "";
16420
+ let lastUsageInfo = {};
16421
+ let toolsUsed = [];
16422
+ let thinkingBlocks = [];
16423
+ let settled = false;
16424
+ const settle = (action) => {
16425
+ if (settled) return;
16426
+ settled = true;
16427
+ this.wsManager.offRequest(requestId);
16428
+ this.wsManager.offDisconnect(onDisconnect);
16429
+ action();
16430
+ };
16431
+ const settleResolve = () => settle(() => resolve3());
16432
+ const settleReject = (err) => settle(() => reject(err));
16433
+ const onDisconnect = () => {
16434
+ logger.debug("[WebSocketLlmBackend] Connection dropped during completion");
16435
+ settleReject(new Error("WebSocket connection lost during completion"));
16436
+ };
16437
+ this.wsManager.onDisconnect(onDisconnect);
16438
+ if (options.abortSignal) {
16439
+ if (options.abortSignal.aborted) {
16440
+ settleResolve();
16441
+ return;
16442
+ }
16443
+ options.abortSignal.addEventListener(
16444
+ "abort",
16445
+ () => {
16446
+ logger.debug("[WebSocketLlmBackend] Abort signal received");
16447
+ settleResolve();
16448
+ },
16449
+ { once: true }
16450
+ );
16451
+ }
16452
+ const updateUsage = (usage) => {
16453
+ if (usage) {
16454
+ lastUsageInfo = { inputTokens: usage.inputTokens, outputTokens: usage.outputTokens };
16455
+ }
16456
+ };
16457
+ this.wsManager.onRequest(requestId, (message) => {
16458
+ if (options.abortSignal?.aborted) return;
16459
+ const action = message.action;
16460
+ if (action === "cli_completion_chunk") {
16461
+ eventCount++;
16462
+ const chunk = message.chunk;
16463
+ streamLogger.onEvent(eventCount, JSON.stringify(chunk));
16464
+ const textChunk = chunk.text || "";
16465
+ if (textChunk) accumulatedText += textChunk;
16466
+ updateUsage(chunk.usage);
16467
+ if (chunk.type === "content") {
16468
+ streamLogger.onContent(eventCount, textChunk, accumulatedText);
16469
+ } else if (chunk.type === "tool_use") {
16470
+ streamLogger.onCriticalEvent(eventCount, "TOOL_USE", `tools: ${chunk.tools?.length}`);
16471
+ if (chunk.tools && chunk.tools.length > 0) toolsUsed = chunk.tools;
16472
+ if (chunk.thinking && chunk.thinking.length > 0) thinkingBlocks = chunk.thinking;
16473
+ }
16474
+ } else if (action === "cli_completion_done") {
16475
+ streamLogger.streamComplete(accumulatedText);
16476
+ const cleanedText = stripThinkingBlocks2(accumulatedText);
16477
+ if (!cleanedText && toolsUsed.length === 0) {
16478
+ settleResolve();
16479
+ return;
16480
+ }
16481
+ const info = {
16482
+ ...lastUsageInfo,
16483
+ ...toolsUsed.length > 0 && { toolsUsed },
16484
+ ...thinkingBlocks.length > 0 && { thinking: thinkingBlocks }
16485
+ };
16486
+ callback([cleanedText], info).then(() => settleResolve()).catch((err) => settleReject(err));
16487
+ } else if (action === "cli_completion_error") {
16488
+ const errorMsg = message.error || "Server error";
16489
+ streamLogger.onCriticalEvent(eventCount, "ERROR", errorMsg);
16490
+ settleReject(new Error(errorMsg));
16491
+ }
16492
+ });
16493
+ const axiosInstance = this.apiClient.getAxiosInstance();
16494
+ axiosInstance.post(
16495
+ this.wsCompletionUrl,
16496
+ {
16497
+ requestId,
16498
+ model,
16499
+ messages,
16500
+ options: {
16501
+ temperature: options.temperature,
16502
+ maxTokens: options.maxTokens,
16503
+ stream: true,
16504
+ tools: options.tools || []
16505
+ }
16506
+ },
16507
+ { signal: options.abortSignal }
16508
+ ).catch((err) => {
16509
+ const msg = err instanceof Error ? err.message : String(err);
16510
+ settleReject(new Error(`HTTP request failed: ${msg}`));
16511
+ });
16512
+ });
16513
+ }
16514
+ /**
16515
+ * Get available models from server (REST call, not streaming).
16516
+ * Delegates to ApiClient -- same as ServerLlmBackend.
16517
+ */
16518
+ async getModelInfo() {
16519
+ try {
16520
+ logger.debug("[WebSocketLlmBackend] Fetching models from /api/models");
16521
+ const response = await this.apiClient.get("/api/models");
16522
+ if (!response || typeof response !== "object" || !Array.isArray(response.models)) {
16523
+ logger.warn("[WebSocketLlmBackend] Invalid API response format, using fallback models");
16524
+ return this.getFallbackModels();
16525
+ }
16526
+ const filteredModels = response.models.filter(
16527
+ (model) => model.type === "text" && model.supportsTools === true
16528
+ );
16529
+ if (filteredModels.length === 0) {
16530
+ logger.warn("[WebSocketLlmBackend] No CLI-compatible models found, using fallback");
16531
+ return this.getFallbackModels();
16532
+ }
16533
+ logger.debug(`[WebSocketLlmBackend] Loaded ${filteredModels.length} models`);
16534
+ return filteredModels;
16535
+ } catch (error) {
16536
+ logger.warn(
16537
+ `[WebSocketLlmBackend] Failed to fetch models: ${error instanceof Error ? error.message : String(error)}`
16538
+ );
16539
+ return this.getFallbackModels();
16540
+ }
16541
+ }
16542
+ getFallbackModels() {
16543
+ return [
16544
+ { id: "claude-sonnet-4-5-20250929", name: "Claude 4.5 Sonnet" },
16545
+ { id: "claude-3-5-haiku-20241022", name: "Claude 3.5 Haiku" },
16546
+ { id: "gpt-4o", name: "GPT-4o" },
16547
+ { id: "gpt-4o-mini", name: "GPT-4o Mini" }
16548
+ ];
16549
+ }
16550
+ };
16551
+
15975
16552
  // src/llm/NotifyingLlmBackend.ts
15976
16553
  var NotifyingLlmBackend = class {
15977
16554
  constructor(inner, backgroundManager) {
@@ -16006,6 +16583,253 @@ Please acknowledge these background agent results and incorporate them into your
16006
16583
  }
16007
16584
  };
16008
16585
 
16586
+ // src/ws/WebSocketConnectionManager.ts
16587
+ var WebSocketConnectionManager = class {
16588
+ constructor(wsUrl, getToken) {
16589
+ this.ws = null;
16590
+ this.heartbeatInterval = null;
16591
+ this.reconnectAttempts = 0;
16592
+ this.maxReconnectDelay = 3e4;
16593
+ this.handlers = /* @__PURE__ */ new Map();
16594
+ this.disconnectHandlers = /* @__PURE__ */ new Set();
16595
+ this.reconnectTimer = null;
16596
+ this.connected = false;
16597
+ this.connecting = false;
16598
+ this.closed = false;
16599
+ this.wsUrl = wsUrl;
16600
+ this.getToken = getToken;
16601
+ }
16602
+ /**
16603
+ * Connect to the WebSocket server.
16604
+ * Resolves when connection is established, rejects on failure.
16605
+ */
16606
+ async connect() {
16607
+ if (this.connected || this.connecting) return;
16608
+ this.connecting = true;
16609
+ const token = await this.getToken();
16610
+ if (!token) {
16611
+ this.connecting = false;
16612
+ throw new Error("No access token available for WebSocket connection");
16613
+ }
16614
+ return new Promise((resolve3, reject) => {
16615
+ logger.debug(`[WS] Connecting to ${this.wsUrl}...`);
16616
+ this.ws = new WebSocket(this.wsUrl, [`access_token.${token}`]);
16617
+ this.ws.onopen = () => {
16618
+ logger.debug("[WS] Connected");
16619
+ this.connected = true;
16620
+ this.connecting = false;
16621
+ this.reconnectAttempts = 0;
16622
+ this.startHeartbeat();
16623
+ resolve3();
16624
+ };
16625
+ this.ws.onmessage = (event) => {
16626
+ try {
16627
+ const data = typeof event.data === "string" ? event.data : event.data.toString();
16628
+ const message = JSON.parse(data);
16629
+ const requestId = message.requestId;
16630
+ if (requestId && this.handlers.has(requestId)) {
16631
+ this.handlers.get(requestId)(message);
16632
+ } else {
16633
+ logger.debug(`[WS] Unhandled message: ${message.action || "unknown"}`);
16634
+ }
16635
+ } catch (err) {
16636
+ logger.debug(`[WS] Failed to parse message: ${err}`);
16637
+ }
16638
+ };
16639
+ this.ws.onclose = () => {
16640
+ logger.debug("[WS] Connection closed");
16641
+ this.cleanup();
16642
+ this.notifyDisconnect();
16643
+ if (!this.closed) {
16644
+ this.scheduleReconnect();
16645
+ }
16646
+ };
16647
+ this.ws.onerror = (err) => {
16648
+ logger.debug(`[WS] Error: ${err}`);
16649
+ if (this.connecting) {
16650
+ this.connecting = false;
16651
+ this.connected = false;
16652
+ reject(new Error("WebSocket connection failed"));
16653
+ }
16654
+ };
16655
+ });
16656
+ }
16657
+ /** Whether the connection is currently established */
16658
+ get isConnected() {
16659
+ return this.connected;
16660
+ }
16661
+ /**
16662
+ * Send a JSON message over the WebSocket connection.
16663
+ */
16664
+ send(data) {
16665
+ if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
16666
+ throw new Error("WebSocket is not connected");
16667
+ }
16668
+ const payload = JSON.stringify(data);
16669
+ const sizeKB = (payload.length / 1024).toFixed(1);
16670
+ logger.debug(`[WS] Sending ${sizeKB} KB (action: ${data.action})`);
16671
+ if (payload.length > 32e3) {
16672
+ logger.warn(`[WS] Payload ${sizeKB} KB exceeds API Gateway 32 KB frame limit \u2014 connection will be closed`);
16673
+ }
16674
+ this.ws.send(payload);
16675
+ }
16676
+ /**
16677
+ * Register a handler for messages matching a specific requestId.
16678
+ */
16679
+ onRequest(requestId, handler) {
16680
+ this.handlers.set(requestId, handler);
16681
+ }
16682
+ /**
16683
+ * Remove a handler for a specific requestId.
16684
+ */
16685
+ offRequest(requestId) {
16686
+ this.handlers.delete(requestId);
16687
+ }
16688
+ /**
16689
+ * Register a handler that fires when the connection drops.
16690
+ */
16691
+ onDisconnect(handler) {
16692
+ this.disconnectHandlers.add(handler);
16693
+ }
16694
+ /**
16695
+ * Remove a disconnect handler.
16696
+ */
16697
+ offDisconnect(handler) {
16698
+ this.disconnectHandlers.delete(handler);
16699
+ }
16700
+ /**
16701
+ * Close the connection and stop all heartbeat/reconnect logic.
16702
+ */
16703
+ disconnect() {
16704
+ this.closed = true;
16705
+ this.cleanup();
16706
+ if (this.ws) {
16707
+ this.ws.close();
16708
+ this.ws = null;
16709
+ }
16710
+ this.handlers.clear();
16711
+ this.disconnectHandlers.clear();
16712
+ }
16713
+ startHeartbeat() {
16714
+ this.stopHeartbeat();
16715
+ this.heartbeatInterval = setInterval(
16716
+ () => {
16717
+ if (this.ws && this.ws.readyState === WebSocket.OPEN) {
16718
+ this.ws.send(JSON.stringify({ action: "heartbeat" }));
16719
+ logger.debug("[WS] Heartbeat sent");
16720
+ }
16721
+ },
16722
+ 5 * 60 * 1e3
16723
+ );
16724
+ }
16725
+ stopHeartbeat() {
16726
+ if (this.heartbeatInterval) {
16727
+ clearInterval(this.heartbeatInterval);
16728
+ this.heartbeatInterval = null;
16729
+ }
16730
+ }
16731
+ cleanup() {
16732
+ this.connected = false;
16733
+ this.connecting = false;
16734
+ this.stopHeartbeat();
16735
+ if (this.reconnectTimer) {
16736
+ clearTimeout(this.reconnectTimer);
16737
+ this.reconnectTimer = null;
16738
+ }
16739
+ }
16740
+ notifyDisconnect() {
16741
+ for (const handler of this.disconnectHandlers) {
16742
+ try {
16743
+ handler();
16744
+ } catch {
16745
+ }
16746
+ }
16747
+ }
16748
+ scheduleReconnect() {
16749
+ if (this.closed) return;
16750
+ this.reconnectAttempts++;
16751
+ const delay = Math.min(1e3 * Math.pow(2, this.reconnectAttempts - 1), this.maxReconnectDelay);
16752
+ logger.debug(`[WS] Reconnecting in ${delay}ms (attempt ${this.reconnectAttempts})`);
16753
+ this.reconnectTimer = setTimeout(async () => {
16754
+ this.reconnectTimer = null;
16755
+ if (this.closed) return;
16756
+ try {
16757
+ await this.connect();
16758
+ } catch {
16759
+ logger.debug("[WS] Reconnection failed");
16760
+ }
16761
+ }, delay);
16762
+ }
16763
+ };
16764
+
16765
+ // src/ws/WebSocketToolExecutor.ts
16766
+ import { v4 as uuidv412 } from "uuid";
16767
+ var WebSocketToolExecutor = class {
16768
+ constructor(wsManager, tokenGetter) {
16769
+ this.wsManager = wsManager;
16770
+ this.tokenGetter = tokenGetter;
16771
+ }
16772
+ /**
16773
+ * Execute a server-side tool via WebSocket.
16774
+ * Returns the tool result or throws on error.
16775
+ */
16776
+ async execute(toolName, input, abortSignal) {
16777
+ if (!this.wsManager.isConnected) {
16778
+ throw new Error("WebSocket is not connected");
16779
+ }
16780
+ const token = await this.tokenGetter();
16781
+ if (!token) {
16782
+ throw new Error("No access token available");
16783
+ }
16784
+ const requestId = uuidv412();
16785
+ return new Promise((resolve3, reject) => {
16786
+ let settled = false;
16787
+ const settle = (action) => {
16788
+ if (settled) return;
16789
+ settled = true;
16790
+ this.wsManager.offRequest(requestId);
16791
+ this.wsManager.offDisconnect(onDisconnect);
16792
+ action();
16793
+ };
16794
+ const settleResolve = (result) => settle(() => resolve3(result));
16795
+ const settleReject = (err) => settle(() => reject(err));
16796
+ const onDisconnect = () => {
16797
+ settleReject(new Error("WebSocket connection lost during tool execution"));
16798
+ };
16799
+ this.wsManager.onDisconnect(onDisconnect);
16800
+ if (abortSignal) {
16801
+ if (abortSignal.aborted) {
16802
+ settleReject(new Error("Tool execution aborted"));
16803
+ return;
16804
+ }
16805
+ abortSignal.addEventListener("abort", () => settleReject(new Error("Tool execution aborted")), {
16806
+ once: true
16807
+ });
16808
+ }
16809
+ this.wsManager.onRequest(requestId, (message) => {
16810
+ if (message.action === "cli_tool_response") {
16811
+ settleResolve({
16812
+ success: message.success,
16813
+ content: message.content,
16814
+ error: message.error
16815
+ });
16816
+ }
16817
+ });
16818
+ try {
16819
+ this.wsManager.send({
16820
+ action: "cli_tool_request",
16821
+ accessToken: token,
16822
+ requestId,
16823
+ toolName,
16824
+ input
16825
+ });
16826
+ } catch (err) {
16827
+ settleReject(err instanceof Error ? err : new Error(String(err)));
16828
+ }
16829
+ });
16830
+ }
16831
+ };
16832
+
16009
16833
  // src/auth/ApiClient.ts
16010
16834
  import axios11 from "axios";
16011
16835
  var ApiClient = class {
@@ -17911,7 +18735,8 @@ function CliApp() {
17911
18735
  agentStore: null,
17912
18736
  abortController: null,
17913
18737
  contextContent: "",
17914
- backgroundManager: null
18738
+ backgroundManager: null,
18739
+ wsManager: null
17915
18740
  });
17916
18741
  const [isInitialized, setIsInitialized] = useState10(false);
17917
18742
  const [initError, setInitError] = useState10(null);
@@ -17939,6 +18764,10 @@ function CliApp() {
17939
18764
  })
17940
18765
  );
17941
18766
  }
18767
+ if (state.wsManager) {
18768
+ state.wsManager.disconnect();
18769
+ setWebSocketToolExecutor(null);
18770
+ }
17942
18771
  if (state.agent) {
17943
18772
  state.agent.removeAllListeners();
17944
18773
  }
@@ -17957,7 +18786,7 @@ function CliApp() {
17957
18786
  setTimeout(() => {
17958
18787
  process.exit(0);
17959
18788
  }, 100);
17960
- }, [state.session, state.sessionStore, state.mcpManager, state.agent, state.imageStore]);
18789
+ }, [state.session, state.sessionStore, state.mcpManager, state.agent, state.imageStore, state.wsManager]);
17961
18790
  useInput9((input, key) => {
17962
18791
  if (key.escape) {
17963
18792
  const store = useCliStore.getState();
@@ -18032,7 +18861,7 @@ function CliApp() {
18032
18861
  if (!isAuthenticated) {
18033
18862
  console.log("\u2139\uFE0F AI features disabled. Available commands: /login, /help, /config\n");
18034
18863
  const minimalSession = {
18035
- id: uuidv411(),
18864
+ id: uuidv413(),
18036
18865
  name: `Session ${(/* @__PURE__ */ new Date()).toLocaleString()}`,
18037
18866
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
18038
18867
  updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
@@ -18060,10 +18889,45 @@ function CliApp() {
18060
18889
  console.log(`\u{1F30D} API Environment: ${envName} (${apiBaseURL})`);
18061
18890
  }
18062
18891
  const apiClient = new ApiClient(apiBaseURL, state.configStore);
18063
- const llm = new ServerLlmBackend({
18064
- apiClient,
18065
- model: config.defaultModel
18066
- });
18892
+ const tokenGetter = async () => {
18893
+ const tokens = await state.configStore.getAuthTokens();
18894
+ return tokens?.accessToken ?? null;
18895
+ };
18896
+ let wsManager = null;
18897
+ let llm;
18898
+ try {
18899
+ const serverConfig = await apiClient.get(
18900
+ "/api/settings/serverConfig"
18901
+ );
18902
+ const wsUrl = serverConfig?.websocketUrl;
18903
+ const wsCompletionUrl = serverConfig?.wsCompletionUrl;
18904
+ if (wsUrl && wsCompletionUrl) {
18905
+ wsManager = new WebSocketConnectionManager(wsUrl, tokenGetter);
18906
+ await wsManager.connect();
18907
+ const wsToolExecutor2 = new WebSocketToolExecutor(wsManager, tokenGetter);
18908
+ setWebSocketToolExecutor(wsToolExecutor2);
18909
+ llm = new WebSocketLlmBackend({
18910
+ wsManager,
18911
+ apiClient,
18912
+ model: config.defaultModel,
18913
+ tokenGetter,
18914
+ wsCompletionUrl
18915
+ });
18916
+ logger.debug("\u{1F50C} Using WebSocket transport (bypasses CloudFront timeout)");
18917
+ } else {
18918
+ throw new Error("No websocketUrl or wsCompletionUrl in server config");
18919
+ }
18920
+ } catch (wsError) {
18921
+ logger.debug(
18922
+ `[WS] WebSocket unavailable, using SSE fallback: ${wsError instanceof Error ? wsError.message : String(wsError)}`
18923
+ );
18924
+ wsManager = null;
18925
+ setWebSocketToolExecutor(null);
18926
+ llm = new ServerLlmBackend({
18927
+ apiClient,
18928
+ model: config.defaultModel
18929
+ });
18930
+ }
18067
18931
  const models = await llm.getModelInfo();
18068
18932
  if (models.length === 0) {
18069
18933
  throw new Error("No models available from server.");
@@ -18076,7 +18940,7 @@ function CliApp() {
18076
18940
  }
18077
18941
  llm.currentModel = modelInfo.id;
18078
18942
  const newSession = {
18079
- id: uuidv411(),
18943
+ id: uuidv413(),
18080
18944
  name: `Session ${(/* @__PURE__ */ new Date()).toLocaleString()}`,
18081
18945
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
18082
18946
  updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
@@ -18290,8 +19154,10 @@ function CliApp() {
18290
19154
  // Store agent store for agent management commands
18291
19155
  contextContent: contextResult.mergedContent,
18292
19156
  // Store raw context for compact instructions
18293
- backgroundManager
19157
+ backgroundManager,
18294
19158
  // Store for grouped notification turn tracking
19159
+ wsManager
19160
+ // WebSocket connection manager (null if using SSE fallback)
18295
19161
  }));
18296
19162
  setStoreSession(newSession);
18297
19163
  const bannerLines = [
@@ -18386,13 +19252,13 @@ function CliApp() {
18386
19252
  messageContent = multimodalMessage.content;
18387
19253
  }
18388
19254
  const userMessage = {
18389
- id: uuidv411(),
19255
+ id: uuidv413(),
18390
19256
  role: "user",
18391
19257
  content: userMessageContent,
18392
19258
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
18393
19259
  };
18394
19260
  const pendingAssistantMessage = {
18395
- id: uuidv411(),
19261
+ id: uuidv413(),
18396
19262
  role: "assistant",
18397
19263
  content: "...",
18398
19264
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
@@ -18605,13 +19471,13 @@ function CliApp() {
18605
19471
  userMessageContent = message;
18606
19472
  }
18607
19473
  const userMessage = {
18608
- id: uuidv411(),
19474
+ id: uuidv413(),
18609
19475
  role: "user",
18610
19476
  content: userMessageContent,
18611
19477
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
18612
19478
  };
18613
19479
  const pendingAssistantMessage = {
18614
- id: uuidv411(),
19480
+ id: uuidv413(),
18615
19481
  role: "assistant",
18616
19482
  content: "...",
18617
19483
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
@@ -18691,7 +19557,7 @@ function CliApp() {
18691
19557
  const currentSession = useCliStore.getState().session;
18692
19558
  if (currentSession) {
18693
19559
  const cancelMessage = {
18694
- id: uuidv411(),
19560
+ id: uuidv413(),
18695
19561
  role: "assistant",
18696
19562
  content: "\u26A0\uFE0F Operation cancelled by user",
18697
19563
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
@@ -18736,7 +19602,7 @@ function CliApp() {
18736
19602
  setState((prev) => ({ ...prev, abortController }));
18737
19603
  try {
18738
19604
  const pendingAssistantMessage = {
18739
- id: uuidv411(),
19605
+ id: uuidv413(),
18740
19606
  role: "assistant",
18741
19607
  content: "...",
18742
19608
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
@@ -18764,7 +19630,7 @@ function CliApp() {
18764
19630
  const currentSession = useCliStore.getState().session;
18765
19631
  if (!currentSession) return;
18766
19632
  const continuationMessage = {
18767
- id: uuidv411(),
19633
+ id: uuidv413(),
18768
19634
  role: "assistant",
18769
19635
  content: "---\n\n**Background Agent Results:**\n\n" + result.finalAnswer,
18770
19636
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
@@ -18825,13 +19691,13 @@ function CliApp() {
18825
19691
  isError = true;
18826
19692
  }
18827
19693
  const userMessage = {
18828
- id: uuidv411(),
19694
+ id: uuidv413(),
18829
19695
  role: "user",
18830
19696
  content: `$ ${command}`,
18831
19697
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
18832
19698
  };
18833
19699
  const assistantMessage = {
18834
- id: uuidv411(),
19700
+ id: uuidv413(),
18835
19701
  role: "assistant",
18836
19702
  content: isError ? `\u274C Error:
18837
19703
  ${output}` : output.trim() || "(no output)",
@@ -19300,7 +20166,7 @@ Keyboard Shortcuts:
19300
20166
  console.clear();
19301
20167
  const model = state.session?.model || state.config?.defaultModel || "claude-sonnet";
19302
20168
  const newSession = {
19303
- id: uuidv411(),
20169
+ id: uuidv413(),
19304
20170
  name: `Session ${(/* @__PURE__ */ new Date()).toLocaleString()}`,
19305
20171
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
19306
20172
  updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
@@ -19805,9 +20671,9 @@ No usage data available for the last ${USAGE_DAYS} days.`);
19805
20671
  return { ...prev, config: updatedConfig };
19806
20672
  });
19807
20673
  if (modelChanged && state.agent) {
19808
- const llm = state.agent.context.llm;
19809
- if (llm) {
19810
- llm.currentModel = updatedConfig.defaultModel;
20674
+ const backend = state.agent.context.llm;
20675
+ if (backend) {
20676
+ backend.currentModel = updatedConfig.defaultModel;
19811
20677
  }
19812
20678
  }
19813
20679
  };