@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/{artifactExtractor-22AVFN7A.js → artifactExtractor-Z6CL6QFN.js} +1 -1
- package/dist/{chunk-DEW32L4X.js → chunk-2LLA4MTN.js} +2 -2
- package/dist/{chunk-E77VWEKZ.js → chunk-GE7Q64MS.js} +120 -13
- package/dist/{chunk-FZXNUST6.js → chunk-IXIOYIPO.js} +6 -6
- package/dist/{chunk-24JZFYBV.js → chunk-RI45VJW3.js} +57 -2
- package/dist/{chunk-6HWTNX47.js → chunk-T67NGQW6.js} +2 -2
- package/dist/{chunk-U4HDDXWT.js → chunk-ZOWCX4MQ.js} +2 -2
- package/dist/commands/doctorCommand.js +1 -1
- package/dist/commands/updateCommand.js +1 -1
- package/dist/{create-LTISVVKL.js → create-JNUW7ICC.js} +3 -3
- package/dist/index.js +898 -32
- package/dist/{llmMarkdownGenerator-DF7EFQZW.js → llmMarkdownGenerator-KGA4HTQN.js} +1 -1
- package/dist/{markdownGenerator-6TAH7OEH.js → markdownGenerator-ERG7FI5H.js} +1 -1
- package/dist/{mementoService-BY5ACS3K.js → mementoService-I56R5DNA.js} +3 -3
- package/dist/{src-C5QSTGEZ.js → src-EMANOLFK.js} +6 -2
- package/dist/{src-ZXFQ5Y4O.js → src-IAR65K73.js} +15 -1
- package/dist/{subtractCredits-V645IMXQ.js → subtractCredits-3MEQF5CV.js} +3 -3
- package/package.json +6 -6
package/dist/index.js
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import "./chunk-GQGOWACU.js";
|
|
3
|
-
import "./chunk-
|
|
4
|
-
import "./chunk-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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
|
|
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
|
|
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
|
-
|
|
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:
|
|
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
|
|
18064
|
-
|
|
18065
|
-
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
|
19809
|
-
if (
|
|
19810
|
-
|
|
20674
|
+
const backend = state.agent.context.llm;
|
|
20675
|
+
if (backend) {
|
|
20676
|
+
backend.currentModel = updatedConfig.defaultModel;
|
|
19811
20677
|
}
|
|
19812
20678
|
}
|
|
19813
20679
|
};
|