@bike4mind/cli 0.2.30 → 0.2.31-b4m-cli-undo-command.19493
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/{HydrationEngine-WGYKF46H.js → HydrationEngine-YL2HWJ3V.js} +1 -1
- package/dist/{artifactExtractor-JQMWU272.js → artifactExtractor-Z6CL6QFN.js} +1 -1
- package/dist/{chunk-LLA44SW7.js → chunk-2LLA4MTN.js} +2 -2
- package/dist/{chunk-T4VPCTBM.js → chunk-GE7Q64MS.js} +834 -5
- package/dist/{chunk-RUI6HNLO.js → chunk-GQGOWACU.js} +8 -4
- package/dist/{chunk-BQAPZP5Y.js → chunk-RI45VJW3.js} +234 -16
- package/dist/{chunk-WIB75EEX.js → chunk-T67NGQW6.js} +2 -2
- package/dist/{chunk-U63VANTQ.js → chunk-ZOWCX4MQ.js} +2 -2
- package/dist/{create-4WOBQ5JM.js → create-JNUW7ICC.js} +3 -3
- package/dist/index.js +3241 -414
- package/dist/{llmMarkdownGenerator-2O24TMGD.js → llmMarkdownGenerator-KGA4HTQN.js} +1 -1
- package/dist/{markdownGenerator-6LAK3FQM.js → markdownGenerator-ERG7FI5H.js} +1 -1
- package/dist/{mementoService-NRR7NQ4C.js → mementoService-I56R5DNA.js} +3 -3
- package/dist/{src-3FQBHGOM.js → src-EMANOLFK.js} +12 -2
- package/dist/{src-VMQ5TKYR.js → src-IAR65K73.js} +27 -1
- package/dist/{subtractCredits-HK72MSDY.js → subtractCredits-3MEQF5CV.js} +3 -3
- package/package.json +6 -6
package/dist/index.js
CHANGED
|
@@ -5,8 +5,8 @@ import {
|
|
|
5
5
|
getEffectiveApiKey,
|
|
6
6
|
getOpenWeatherKey,
|
|
7
7
|
getSerperKey
|
|
8
|
-
} from "./chunk-
|
|
9
|
-
import "./chunk-
|
|
8
|
+
} from "./chunk-T67NGQW6.js";
|
|
9
|
+
import "./chunk-GQGOWACU.js";
|
|
10
10
|
import {
|
|
11
11
|
ConfigStore,
|
|
12
12
|
logger
|
|
@@ -15,8 +15,8 @@ import {
|
|
|
15
15
|
selectActiveBackgroundAgents,
|
|
16
16
|
useCliStore
|
|
17
17
|
} from "./chunk-BYXFQJYT.js";
|
|
18
|
-
import "./chunk-
|
|
19
|
-
import "./chunk-
|
|
18
|
+
import "./chunk-2LLA4MTN.js";
|
|
19
|
+
import "./chunk-ZOWCX4MQ.js";
|
|
20
20
|
import {
|
|
21
21
|
BFLImageService,
|
|
22
22
|
BaseStorage,
|
|
@@ -28,7 +28,7 @@ import {
|
|
|
28
28
|
OpenAIBackend,
|
|
29
29
|
OpenAIImageService,
|
|
30
30
|
XAIImageService
|
|
31
|
-
} from "./chunk-
|
|
31
|
+
} from "./chunk-RI45VJW3.js";
|
|
32
32
|
import {
|
|
33
33
|
AiEvents,
|
|
34
34
|
ApiKeyEvents,
|
|
@@ -83,8 +83,10 @@ import {
|
|
|
83
83
|
VideoModels,
|
|
84
84
|
XAI_IMAGE_MODELS,
|
|
85
85
|
b4mLLMTools,
|
|
86
|
-
getMcpProviderMetadata
|
|
87
|
-
|
|
86
|
+
getMcpProviderMetadata,
|
|
87
|
+
getViewById,
|
|
88
|
+
resolveNavigationIntents
|
|
89
|
+
} from "./chunk-GE7Q64MS.js";
|
|
88
90
|
import {
|
|
89
91
|
Logger
|
|
90
92
|
} from "./chunk-OCYRD7D6.js";
|
|
@@ -94,7 +96,7 @@ import React21, { useState as useState10, useEffect as useEffect7, useCallback a
|
|
|
94
96
|
import { render, Box as Box20, Text as Text20, useApp, useInput as useInput9 } from "ink";
|
|
95
97
|
import { execSync } from "child_process";
|
|
96
98
|
import { randomBytes as randomBytes5 } from "crypto";
|
|
97
|
-
import { v4 as
|
|
99
|
+
import { v4 as uuidv413 } from "uuid";
|
|
98
100
|
|
|
99
101
|
// src/components/App.tsx
|
|
100
102
|
import React15, { useState as useState6, useEffect as useEffect5 } from "react";
|
|
@@ -350,9 +352,23 @@ function CommandAutocomplete({ commands, selectedIndex }) {
|
|
|
350
352
|
if (commands.length === 0) {
|
|
351
353
|
return /* @__PURE__ */ React3.createElement(Box2, { marginLeft: 2, marginTop: 1 }, /* @__PURE__ */ React3.createElement(Text3, { dimColor: true }, "No matching commands"));
|
|
352
354
|
}
|
|
353
|
-
|
|
355
|
+
const VIEWPORT_SIZE = 6;
|
|
356
|
+
const totalCommands = commands.length;
|
|
357
|
+
let startIndex = 0;
|
|
358
|
+
let endIndex = Math.min(VIEWPORT_SIZE, totalCommands);
|
|
359
|
+
if (totalCommands > VIEWPORT_SIZE) {
|
|
360
|
+
const halfViewport = Math.floor(VIEWPORT_SIZE / 2);
|
|
361
|
+
startIndex = Math.max(0, selectedIndex - halfViewport);
|
|
362
|
+
endIndex = Math.min(totalCommands, startIndex + VIEWPORT_SIZE);
|
|
363
|
+
if (endIndex === totalCommands) {
|
|
364
|
+
startIndex = Math.max(0, totalCommands - VIEWPORT_SIZE);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
const visibleCommands = commands.slice(startIndex, endIndex);
|
|
368
|
+
return /* @__PURE__ */ React3.createElement(Box2, { flexDirection: "column", marginLeft: 2, marginTop: 1 }, /* @__PURE__ */ React3.createElement(Box2, { marginBottom: 1 }, /* @__PURE__ */ React3.createElement(Text3, { dimColor: true }, totalCommands === 1 ? "1 command" : `${totalCommands} commands`, totalCommands > VIEWPORT_SIZE && ` (${selectedIndex + 1}/${totalCommands})`, " \u2022 Use \u2191\u2193 to navigate, Enter to select")), visibleCommands.map((cmd, viewportIndex) => {
|
|
369
|
+
const actualIndex = startIndex + viewportIndex;
|
|
354
370
|
const args = cmd.args ? ` ${cmd.args}` : "";
|
|
355
|
-
const isSelected =
|
|
371
|
+
const isSelected = actualIndex === selectedIndex;
|
|
356
372
|
const sourceIcon = cmd.source === "global" ? "\u{1F3E0} " : cmd.source === "project" ? "\u{1F4C1} " : cmd.source === "built-in" ? "\u{1F527} " : "";
|
|
357
373
|
return /* @__PURE__ */ React3.createElement(Box2, { key: cmd.name, marginLeft: 1 }, /* @__PURE__ */ React3.createElement(Text3, { color: isSelected ? "cyan" : void 0, bold: isSelected }, isSelected ? "\u25B8 " : " ", sourceIcon, "/", cmd.name, args, " - ", cmd.description));
|
|
358
374
|
}));
|
|
@@ -713,8 +729,25 @@ var COMMANDS = [
|
|
|
713
729
|
},
|
|
714
730
|
{
|
|
715
731
|
name: "rewind",
|
|
716
|
-
description: "Rewind conversation to a previous point"
|
|
717
|
-
|
|
732
|
+
description: "Rewind conversation to a previous point"
|
|
733
|
+
},
|
|
734
|
+
{
|
|
735
|
+
name: "undo",
|
|
736
|
+
description: "Undo the last file change"
|
|
737
|
+
},
|
|
738
|
+
{
|
|
739
|
+
name: "checkpoints",
|
|
740
|
+
description: "List available file restore points"
|
|
741
|
+
},
|
|
742
|
+
{
|
|
743
|
+
name: "restore",
|
|
744
|
+
description: "Restore files to a specific checkpoint",
|
|
745
|
+
args: "<number>"
|
|
746
|
+
},
|
|
747
|
+
{
|
|
748
|
+
name: "diff",
|
|
749
|
+
description: "Show diff between current state and a checkpoint",
|
|
750
|
+
args: "[number]"
|
|
718
751
|
},
|
|
719
752
|
{
|
|
720
753
|
name: "project-config",
|
|
@@ -1096,8 +1129,8 @@ function InputPrompt({
|
|
|
1096
1129
|
useEffect3(() => {
|
|
1097
1130
|
onBashModeChange?.(isBashMode);
|
|
1098
1131
|
}, [isBashMode, onBashModeChange]);
|
|
1099
|
-
const
|
|
1100
|
-
const
|
|
1132
|
+
const commandQuery = value.startsWith("/") ? value.slice(1) : "";
|
|
1133
|
+
const shouldShowCommandAutocomplete = value.startsWith("/") && !disabled && !fileAutocomplete?.active && !looksLikeFilePath(value) && !pastedContent && !commandQuery.includes(" ");
|
|
1101
1134
|
const filteredCommands = useMemo(() => {
|
|
1102
1135
|
if (!shouldShowCommandAutocomplete) return [];
|
|
1103
1136
|
return searchCommands(commandQuery, commands);
|
|
@@ -2818,9 +2851,327 @@ var CommandHistoryStore = class {
|
|
|
2818
2851
|
}
|
|
2819
2852
|
};
|
|
2820
2853
|
|
|
2821
|
-
// src/storage/
|
|
2822
|
-
import fs5 from "fs
|
|
2854
|
+
// src/storage/CheckpointStore.ts
|
|
2855
|
+
import { promises as fs5 } from "fs";
|
|
2856
|
+
import { existsSync as existsSync4, readFileSync as readFileSync4, writeFileSync, unlinkSync } from "fs";
|
|
2857
|
+
import { execFileSync } from "child_process";
|
|
2823
2858
|
import path6 from "path";
|
|
2859
|
+
var MAX_FILE_SIZE2 = 10 * 1024 * 1024;
|
|
2860
|
+
var DEFAULT_KEEP_COUNT = 50;
|
|
2861
|
+
var ABSENT_MARKER = ".b4m-absent";
|
|
2862
|
+
var CheckpointStore = class {
|
|
2863
|
+
constructor(projectDir) {
|
|
2864
|
+
this.metadata = null;
|
|
2865
|
+
this.sessionId = null;
|
|
2866
|
+
this.initialized = false;
|
|
2867
|
+
this.projectDir = projectDir;
|
|
2868
|
+
this.shadowRepoDir = path6.join(projectDir, ".b4m", "shadow-repo");
|
|
2869
|
+
this.metadataPath = path6.join(projectDir, ".b4m", "checkpoints.json");
|
|
2870
|
+
}
|
|
2871
|
+
/**
|
|
2872
|
+
* Initialize the shadow git repository and load metadata
|
|
2873
|
+
*/
|
|
2874
|
+
async init(sessionId) {
|
|
2875
|
+
this.sessionId = sessionId;
|
|
2876
|
+
await fs5.mkdir(path6.join(this.projectDir, ".b4m"), { recursive: true });
|
|
2877
|
+
if (!existsSync4(path6.join(this.shadowRepoDir, ".git"))) {
|
|
2878
|
+
await fs5.mkdir(this.shadowRepoDir, { recursive: true });
|
|
2879
|
+
this.git("init");
|
|
2880
|
+
this.git("config", "user.email", "checkpoint@b4m.local");
|
|
2881
|
+
this.git("config", "user.name", "B4M Checkpoint");
|
|
2882
|
+
this.git("commit", "--allow-empty", "-m", "checkpoint-init");
|
|
2883
|
+
}
|
|
2884
|
+
await this.loadMetadata();
|
|
2885
|
+
await this.ensureGitignore();
|
|
2886
|
+
await this.pruneCheckpoints(DEFAULT_KEEP_COUNT);
|
|
2887
|
+
this.initialized = true;
|
|
2888
|
+
}
|
|
2889
|
+
/**
|
|
2890
|
+
* Update session ID (e.g., on /clear or /resume)
|
|
2891
|
+
*/
|
|
2892
|
+
setSessionId(sessionId) {
|
|
2893
|
+
this.sessionId = sessionId;
|
|
2894
|
+
}
|
|
2895
|
+
/**
|
|
2896
|
+
* Create a checkpoint by snapshotting the current state of target files
|
|
2897
|
+
* before they are modified by a tool.
|
|
2898
|
+
*
|
|
2899
|
+
* @param toolName - The tool about to modify files
|
|
2900
|
+
* @param filePaths - Relative paths of files about to be modified
|
|
2901
|
+
* @param name - Optional human-readable checkpoint name
|
|
2902
|
+
*/
|
|
2903
|
+
async createCheckpoint(toolName, filePaths, name) {
|
|
2904
|
+
if (!this.initialized || !this.sessionId) {
|
|
2905
|
+
return null;
|
|
2906
|
+
}
|
|
2907
|
+
const checkpointName = name || `before-${toolName}-${path6.basename(filePaths[0] || "unknown")}`;
|
|
2908
|
+
try {
|
|
2909
|
+
let hasChanges = false;
|
|
2910
|
+
for (const filePath of filePaths) {
|
|
2911
|
+
const absolutePath = path6.resolve(this.projectDir, filePath);
|
|
2912
|
+
const shadowPath = path6.join(this.shadowRepoDir, filePath);
|
|
2913
|
+
const shadowDir = path6.dirname(shadowPath);
|
|
2914
|
+
const absentMarkerPath = path6.join(shadowDir, `${path6.basename(filePath)}${ABSENT_MARKER}`);
|
|
2915
|
+
await fs5.mkdir(shadowDir, { recursive: true });
|
|
2916
|
+
if (existsSync4(absolutePath)) {
|
|
2917
|
+
const stats = await fs5.stat(absolutePath);
|
|
2918
|
+
if (stats.size > MAX_FILE_SIZE2) {
|
|
2919
|
+
continue;
|
|
2920
|
+
}
|
|
2921
|
+
if (existsSync4(absentMarkerPath)) {
|
|
2922
|
+
await fs5.unlink(absentMarkerPath);
|
|
2923
|
+
}
|
|
2924
|
+
await fs5.copyFile(absolutePath, shadowPath);
|
|
2925
|
+
hasChanges = true;
|
|
2926
|
+
} else {
|
|
2927
|
+
if (existsSync4(shadowPath)) {
|
|
2928
|
+
await fs5.unlink(shadowPath);
|
|
2929
|
+
}
|
|
2930
|
+
await fs5.writeFile(absentMarkerPath, "", "utf-8");
|
|
2931
|
+
hasChanges = true;
|
|
2932
|
+
}
|
|
2933
|
+
}
|
|
2934
|
+
if (!hasChanges) {
|
|
2935
|
+
return null;
|
|
2936
|
+
}
|
|
2937
|
+
this.git("add", "-A");
|
|
2938
|
+
try {
|
|
2939
|
+
this.git("diff", "--cached", "--quiet");
|
|
2940
|
+
return null;
|
|
2941
|
+
} catch {
|
|
2942
|
+
}
|
|
2943
|
+
this.git("commit", "-m", checkpointName);
|
|
2944
|
+
const sha = this.git("rev-parse", "--short", "HEAD").trim();
|
|
2945
|
+
const checkpoint = {
|
|
2946
|
+
id: sha,
|
|
2947
|
+
name: checkpointName,
|
|
2948
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2949
|
+
toolName,
|
|
2950
|
+
filePaths: [...filePaths],
|
|
2951
|
+
sessionId: this.sessionId
|
|
2952
|
+
};
|
|
2953
|
+
if (!this.metadata) {
|
|
2954
|
+
this.metadata = { checkpoints: [], createdAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
2955
|
+
}
|
|
2956
|
+
this.metadata.checkpoints.push(checkpoint);
|
|
2957
|
+
await this.saveMetadata();
|
|
2958
|
+
return checkpoint;
|
|
2959
|
+
} catch {
|
|
2960
|
+
return null;
|
|
2961
|
+
}
|
|
2962
|
+
}
|
|
2963
|
+
/**
|
|
2964
|
+
* List checkpoints for the current session (most recent first)
|
|
2965
|
+
*/
|
|
2966
|
+
listCheckpoints() {
|
|
2967
|
+
if (!this.metadata || !this.sessionId) {
|
|
2968
|
+
return [];
|
|
2969
|
+
}
|
|
2970
|
+
return this.metadata.checkpoints.filter((cp) => cp.sessionId === this.sessionId).reverse();
|
|
2971
|
+
}
|
|
2972
|
+
/**
|
|
2973
|
+
* Get a specific checkpoint by 1-based index (1 = most recent)
|
|
2974
|
+
*/
|
|
2975
|
+
getCheckpoint(index) {
|
|
2976
|
+
const checkpoints = this.listCheckpoints();
|
|
2977
|
+
if (index < 1 || index > checkpoints.length) {
|
|
2978
|
+
return null;
|
|
2979
|
+
}
|
|
2980
|
+
return checkpoints[index - 1];
|
|
2981
|
+
}
|
|
2982
|
+
/**
|
|
2983
|
+
* Restore files to the state captured in a specific checkpoint
|
|
2984
|
+
*
|
|
2985
|
+
* @param index - 1-based index (1 = most recent)
|
|
2986
|
+
* @returns The checkpoint that was restored to
|
|
2987
|
+
*/
|
|
2988
|
+
async restoreCheckpoint(index) {
|
|
2989
|
+
const checkpoint = this.getCheckpoint(index);
|
|
2990
|
+
if (!checkpoint) {
|
|
2991
|
+
throw new Error(`Checkpoint #${index} not found. Use /checkpoints to see available restore points.`);
|
|
2992
|
+
}
|
|
2993
|
+
for (const filePath of checkpoint.filePaths) {
|
|
2994
|
+
const absolutePath = path6.resolve(this.projectDir, filePath);
|
|
2995
|
+
try {
|
|
2996
|
+
const content = this.git("show", `${checkpoint.id}:${filePath}`);
|
|
2997
|
+
await fs5.mkdir(path6.dirname(absolutePath), { recursive: true });
|
|
2998
|
+
await fs5.writeFile(absolutePath, content, "utf-8");
|
|
2999
|
+
} catch {
|
|
3000
|
+
try {
|
|
3001
|
+
this.git("show", `${checkpoint.id}:${path6.dirname(filePath)}/${path6.basename(filePath)}${ABSENT_MARKER}`);
|
|
3002
|
+
if (existsSync4(absolutePath)) {
|
|
3003
|
+
await fs5.unlink(absolutePath);
|
|
3004
|
+
}
|
|
3005
|
+
} catch {
|
|
3006
|
+
}
|
|
3007
|
+
}
|
|
3008
|
+
}
|
|
3009
|
+
return checkpoint;
|
|
3010
|
+
}
|
|
3011
|
+
/**
|
|
3012
|
+
* Undo the last file change (restore to most recent checkpoint)
|
|
3013
|
+
*/
|
|
3014
|
+
async undoLast() {
|
|
3015
|
+
return this.restoreCheckpoint(1);
|
|
3016
|
+
}
|
|
3017
|
+
/**
|
|
3018
|
+
* Get diff between current file state and a checkpoint
|
|
3019
|
+
*
|
|
3020
|
+
* @param index - 1-based index (1 = most recent, default)
|
|
3021
|
+
* @returns Unified diff string
|
|
3022
|
+
*/
|
|
3023
|
+
getCheckpointDiff(index = 1) {
|
|
3024
|
+
const checkpoint = this.getCheckpoint(index);
|
|
3025
|
+
if (!checkpoint) {
|
|
3026
|
+
throw new Error(`Checkpoint #${index} not found. Use /checkpoints to see available restore points.`);
|
|
3027
|
+
}
|
|
3028
|
+
const diffParts = [];
|
|
3029
|
+
for (const filePath of checkpoint.filePaths) {
|
|
3030
|
+
const absolutePath = path6.resolve(this.projectDir, filePath);
|
|
3031
|
+
try {
|
|
3032
|
+
let checkpointContent;
|
|
3033
|
+
try {
|
|
3034
|
+
checkpointContent = this.git("show", `${checkpoint.id}:${filePath}`);
|
|
3035
|
+
} catch {
|
|
3036
|
+
checkpointContent = "";
|
|
3037
|
+
}
|
|
3038
|
+
let currentContent = "";
|
|
3039
|
+
if (existsSync4(absolutePath)) {
|
|
3040
|
+
currentContent = readFileSync4(absolutePath, "utf-8");
|
|
3041
|
+
}
|
|
3042
|
+
if (checkpointContent === currentContent) {
|
|
3043
|
+
continue;
|
|
3044
|
+
}
|
|
3045
|
+
const tmpCheckpoint = path6.join(this.shadowRepoDir, ".diff-a");
|
|
3046
|
+
const tmpCurrent = path6.join(this.shadowRepoDir, ".diff-b");
|
|
3047
|
+
writeFileSync(tmpCheckpoint, checkpointContent, "utf-8");
|
|
3048
|
+
writeFileSync(tmpCurrent, currentContent, "utf-8");
|
|
3049
|
+
try {
|
|
3050
|
+
this.git(
|
|
3051
|
+
"diff",
|
|
3052
|
+
"--no-index",
|
|
3053
|
+
"--color",
|
|
3054
|
+
`--src-prefix=checkpoint:`,
|
|
3055
|
+
`--dst-prefix=current:`,
|
|
3056
|
+
tmpCheckpoint,
|
|
3057
|
+
tmpCurrent
|
|
3058
|
+
);
|
|
3059
|
+
} catch (diffError) {
|
|
3060
|
+
if (diffError && typeof diffError === "object" && "stdout" in diffError) {
|
|
3061
|
+
const output = diffError.stdout?.toString() || "";
|
|
3062
|
+
if (output) {
|
|
3063
|
+
diffParts.push(`--- ${filePath} (checkpoint #${index})
|
|
3064
|
+
+++ ${filePath} (current)
|
|
3065
|
+
${output}`);
|
|
3066
|
+
}
|
|
3067
|
+
}
|
|
3068
|
+
}
|
|
3069
|
+
try {
|
|
3070
|
+
unlinkSync(tmpCheckpoint);
|
|
3071
|
+
unlinkSync(tmpCurrent);
|
|
3072
|
+
} catch {
|
|
3073
|
+
}
|
|
3074
|
+
} catch {
|
|
3075
|
+
}
|
|
3076
|
+
}
|
|
3077
|
+
return diffParts.join("\n");
|
|
3078
|
+
}
|
|
3079
|
+
/**
|
|
3080
|
+
* Prune old checkpoints beyond the keep count
|
|
3081
|
+
*/
|
|
3082
|
+
async pruneCheckpoints(keepCount = DEFAULT_KEEP_COUNT) {
|
|
3083
|
+
if (!this.metadata) return;
|
|
3084
|
+
const total = this.metadata.checkpoints.length;
|
|
3085
|
+
if (total <= keepCount) return;
|
|
3086
|
+
this.metadata.checkpoints = this.metadata.checkpoints.slice(-keepCount);
|
|
3087
|
+
await this.saveMetadata();
|
|
3088
|
+
try {
|
|
3089
|
+
this.git("gc", "--auto", "--quiet");
|
|
3090
|
+
} catch {
|
|
3091
|
+
}
|
|
3092
|
+
}
|
|
3093
|
+
/**
|
|
3094
|
+
* Clean up the shadow repository entirely
|
|
3095
|
+
*/
|
|
3096
|
+
async cleanup() {
|
|
3097
|
+
try {
|
|
3098
|
+
await fs5.rm(this.shadowRepoDir, { recursive: true, force: true });
|
|
3099
|
+
if (existsSync4(this.metadataPath)) {
|
|
3100
|
+
await fs5.unlink(this.metadataPath);
|
|
3101
|
+
}
|
|
3102
|
+
this.metadata = null;
|
|
3103
|
+
this.initialized = false;
|
|
3104
|
+
} catch {
|
|
3105
|
+
}
|
|
3106
|
+
}
|
|
3107
|
+
// --- Private helpers ---
|
|
3108
|
+
/**
|
|
3109
|
+
* Execute a git command in the shadow repo
|
|
3110
|
+
*/
|
|
3111
|
+
git(...args) {
|
|
3112
|
+
return execFileSync("git", args, {
|
|
3113
|
+
cwd: this.shadowRepoDir,
|
|
3114
|
+
encoding: "utf-8",
|
|
3115
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
3116
|
+
timeout: 1e4
|
|
3117
|
+
});
|
|
3118
|
+
}
|
|
3119
|
+
/**
|
|
3120
|
+
* Load checkpoint metadata from disk
|
|
3121
|
+
*/
|
|
3122
|
+
async loadMetadata() {
|
|
3123
|
+
try {
|
|
3124
|
+
if (existsSync4(this.metadataPath)) {
|
|
3125
|
+
const data = await fs5.readFile(this.metadataPath, "utf-8");
|
|
3126
|
+
this.metadata = JSON.parse(data);
|
|
3127
|
+
} else {
|
|
3128
|
+
this.metadata = {
|
|
3129
|
+
checkpoints: [],
|
|
3130
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3131
|
+
};
|
|
3132
|
+
}
|
|
3133
|
+
} catch {
|
|
3134
|
+
this.metadata = {
|
|
3135
|
+
checkpoints: [],
|
|
3136
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3137
|
+
};
|
|
3138
|
+
}
|
|
3139
|
+
}
|
|
3140
|
+
/**
|
|
3141
|
+
* Save checkpoint metadata to disk
|
|
3142
|
+
*/
|
|
3143
|
+
async saveMetadata() {
|
|
3144
|
+
if (!this.metadata) return;
|
|
3145
|
+
await fs5.writeFile(this.metadataPath, JSON.stringify(this.metadata, null, 2), "utf-8");
|
|
3146
|
+
}
|
|
3147
|
+
/**
|
|
3148
|
+
* Ensure .b4m/ is in .gitignore
|
|
3149
|
+
*/
|
|
3150
|
+
async ensureGitignore() {
|
|
3151
|
+
const gitignorePath = path6.join(this.projectDir, ".gitignore");
|
|
3152
|
+
const entryToAdd = ".b4m/";
|
|
3153
|
+
try {
|
|
3154
|
+
let content = "";
|
|
3155
|
+
try {
|
|
3156
|
+
content = await fs5.readFile(gitignorePath, "utf-8");
|
|
3157
|
+
} catch {
|
|
3158
|
+
}
|
|
3159
|
+
if (content.includes(entryToAdd) || content.includes(".b4m")) {
|
|
3160
|
+
return;
|
|
3161
|
+
}
|
|
3162
|
+
const newContent = content.trim() + (content ? "\n" : "") + `
|
|
3163
|
+
# B4M checkpoint data
|
|
3164
|
+
${entryToAdd}
|
|
3165
|
+
`;
|
|
3166
|
+
await fs5.writeFile(gitignorePath, newContent, "utf-8");
|
|
3167
|
+
} catch {
|
|
3168
|
+
}
|
|
3169
|
+
}
|
|
3170
|
+
};
|
|
3171
|
+
|
|
3172
|
+
// src/storage/CustomCommandStore.ts
|
|
3173
|
+
import fs6 from "fs/promises";
|
|
3174
|
+
import path7 from "path";
|
|
2824
3175
|
import os from "os";
|
|
2825
3176
|
|
|
2826
3177
|
// src/utils/commandParser.ts
|
|
@@ -2983,14 +3334,14 @@ var CustomCommandStore = class {
|
|
|
2983
3334
|
const home = os.homedir();
|
|
2984
3335
|
const root = projectRoot || process.cwd();
|
|
2985
3336
|
this.globalCommandsDirs = [
|
|
2986
|
-
|
|
2987
|
-
|
|
2988
|
-
|
|
3337
|
+
path7.join(home, ".bike4mind", "commands"),
|
|
3338
|
+
path7.join(home, ".claude", "commands"),
|
|
3339
|
+
path7.join(home, ".claude", "skills")
|
|
2989
3340
|
];
|
|
2990
3341
|
this.projectCommandsDirs = [
|
|
2991
|
-
|
|
2992
|
-
|
|
2993
|
-
|
|
3342
|
+
path7.join(root, ".bike4mind", "commands"),
|
|
3343
|
+
path7.join(root, ".claude", "commands"),
|
|
3344
|
+
path7.join(root, ".claude", "skills")
|
|
2994
3345
|
];
|
|
2995
3346
|
}
|
|
2996
3347
|
/**
|
|
@@ -3015,7 +3366,7 @@ var CustomCommandStore = class {
|
|
|
3015
3366
|
*/
|
|
3016
3367
|
async loadCommandsFromDirectory(directory, source) {
|
|
3017
3368
|
try {
|
|
3018
|
-
const stats = await
|
|
3369
|
+
const stats = await fs6.stat(directory);
|
|
3019
3370
|
if (!stats.isDirectory()) {
|
|
3020
3371
|
return;
|
|
3021
3372
|
}
|
|
@@ -3048,9 +3399,9 @@ var CustomCommandStore = class {
|
|
|
3048
3399
|
async findCommandFiles(directory) {
|
|
3049
3400
|
const files = [];
|
|
3050
3401
|
try {
|
|
3051
|
-
const entries = await
|
|
3402
|
+
const entries = await fs6.readdir(directory, { withFileTypes: true });
|
|
3052
3403
|
for (const entry of entries) {
|
|
3053
|
-
const fullPath =
|
|
3404
|
+
const fullPath = path7.join(directory, entry.name);
|
|
3054
3405
|
if (entry.isDirectory()) {
|
|
3055
3406
|
const subFiles = await this.findCommandFiles(fullPath);
|
|
3056
3407
|
files.push(...subFiles);
|
|
@@ -3070,14 +3421,14 @@ var CustomCommandStore = class {
|
|
|
3070
3421
|
* @param source - Source identifier ('global' or 'project')
|
|
3071
3422
|
*/
|
|
3072
3423
|
async loadCommandFile(filePath, source) {
|
|
3073
|
-
const filename =
|
|
3424
|
+
const filename = path7.basename(filePath);
|
|
3074
3425
|
const isSkillFile = filename.toLowerCase() === "skill.md";
|
|
3075
3426
|
const commandName = isSkillFile ? this.extractSkillName(filePath) : extractCommandName(filename);
|
|
3076
3427
|
if (!commandName) {
|
|
3077
3428
|
console.warn(`Invalid command filename: ${filename} (must end with .md and have valid name)`);
|
|
3078
3429
|
return;
|
|
3079
3430
|
}
|
|
3080
|
-
const fileContent = await
|
|
3431
|
+
const fileContent = await fs6.readFile(filePath, "utf-8");
|
|
3081
3432
|
const command = parseCommandFile(fileContent, filePath, commandName, source);
|
|
3082
3433
|
this.commands.set(commandName, command);
|
|
3083
3434
|
}
|
|
@@ -3089,7 +3440,7 @@ var CustomCommandStore = class {
|
|
|
3089
3440
|
* @returns Skill name or null if invalid
|
|
3090
3441
|
*/
|
|
3091
3442
|
extractSkillName(filePath) {
|
|
3092
|
-
const parentDir =
|
|
3443
|
+
const parentDir = path7.basename(path7.dirname(filePath));
|
|
3093
3444
|
return parentDir && parentDir !== "skills" ? parentDir : null;
|
|
3094
3445
|
}
|
|
3095
3446
|
/**
|
|
@@ -3151,15 +3502,15 @@ var CustomCommandStore = class {
|
|
|
3151
3502
|
*/
|
|
3152
3503
|
async createCommandFile(name, isGlobal = false) {
|
|
3153
3504
|
const targetDir = isGlobal ? this.globalCommandsDirs[0] : this.projectCommandsDirs[0];
|
|
3154
|
-
const filePath =
|
|
3155
|
-
const fileExists = await
|
|
3505
|
+
const filePath = path7.join(targetDir, `${name}.md`);
|
|
3506
|
+
const fileExists = await fs6.access(filePath).then(
|
|
3156
3507
|
() => true,
|
|
3157
3508
|
() => false
|
|
3158
3509
|
);
|
|
3159
3510
|
if (fileExists) {
|
|
3160
3511
|
throw new Error(`Command file already exists: ${filePath}`);
|
|
3161
3512
|
}
|
|
3162
|
-
await
|
|
3513
|
+
await fs6.mkdir(targetDir, { recursive: true });
|
|
3163
3514
|
const template = `---
|
|
3164
3515
|
description: ${name} command
|
|
3165
3516
|
argument-hint: [args]
|
|
@@ -3174,7 +3525,7 @@ You can use:
|
|
|
3174
3525
|
- $1, $2, etc. for positional arguments
|
|
3175
3526
|
- @filename for file references
|
|
3176
3527
|
`;
|
|
3177
|
-
await
|
|
3528
|
+
await fs6.writeFile(filePath, template, "utf-8");
|
|
3178
3529
|
return filePath;
|
|
3179
3530
|
}
|
|
3180
3531
|
};
|
|
@@ -3388,7 +3739,8 @@ ${options.context}` : this.getSystemPrompt()
|
|
|
3388
3739
|
maxTokens,
|
|
3389
3740
|
temperature,
|
|
3390
3741
|
abortSignal: options.signal,
|
|
3391
|
-
tool_choice: this.context.toolChoice
|
|
3742
|
+
tool_choice: this.context.toolChoice,
|
|
3743
|
+
executeTools: false
|
|
3392
3744
|
},
|
|
3393
3745
|
async (texts, completionInfo) => {
|
|
3394
3746
|
for (const text of texts) {
|
|
@@ -4405,7 +4757,7 @@ import { GoogleGenerativeAI } from "@google/generative-ai";
|
|
|
4405
4757
|
import OpenAI2 from "openai";
|
|
4406
4758
|
|
|
4407
4759
|
// ../../b4m-core/packages/services/dist/src/importHistoryService/index.js
|
|
4408
|
-
import
|
|
4760
|
+
import fs7, { unlinkSync as unlinkSync2 } from "fs";
|
|
4409
4761
|
import yauzl from "yauzl";
|
|
4410
4762
|
import axios3 from "axios";
|
|
4411
4763
|
|
|
@@ -6096,8 +6448,8 @@ async function processAndStoreImages(images, context) {
|
|
|
6096
6448
|
const buffer = await downloadImage(image);
|
|
6097
6449
|
const fileType = await fileTypeFromBuffer2(buffer);
|
|
6098
6450
|
const filename = `${uuidv45()}.${fileType?.ext}`;
|
|
6099
|
-
const
|
|
6100
|
-
return
|
|
6451
|
+
const path21 = await context.imageGenerateStorage.upload(buffer, filename, {});
|
|
6452
|
+
return path21;
|
|
6101
6453
|
}));
|
|
6102
6454
|
}
|
|
6103
6455
|
async function updateQuestAndReturnMarkdown(storedImageUrls, context) {
|
|
@@ -7423,8 +7775,8 @@ async function processAndStoreImage(imageUrl, context) {
|
|
|
7423
7775
|
const buffer = await downloadImage2(imageUrl);
|
|
7424
7776
|
const fileType = await fileTypeFromBuffer3(buffer);
|
|
7425
7777
|
const filename = `${uuidv46()}.${fileType?.ext}`;
|
|
7426
|
-
const
|
|
7427
|
-
return
|
|
7778
|
+
const path21 = await context.imageGenerateStorage.upload(buffer, filename, {});
|
|
7779
|
+
return path21;
|
|
7428
7780
|
}
|
|
7429
7781
|
async function generateFullMask(imageBuffer) {
|
|
7430
7782
|
const sharp = (await import("sharp")).default;
|
|
@@ -9057,8 +9409,8 @@ var getHeliocentricCoords = (planet, T) => {
|
|
|
9057
9409
|
const sinNode = Math.sin(longNode);
|
|
9058
9410
|
const x = r * (cosNode * cosOmega - sinNode * sinOmega * cosI);
|
|
9059
9411
|
const y = r * (sinNode * cosOmega + cosNode * sinOmega * cosI);
|
|
9060
|
-
const
|
|
9061
|
-
return { x, y, z:
|
|
9412
|
+
const z148 = r * sinOmega * sinI;
|
|
9413
|
+
return { x, y, z: z148, r };
|
|
9062
9414
|
};
|
|
9063
9415
|
var getGeocentricEcliptic = (planet, earth) => {
|
|
9064
9416
|
const dx = planet.x - earth.x;
|
|
@@ -9365,33 +9717,33 @@ var planetVisibilityTool = {
|
|
|
9365
9717
|
};
|
|
9366
9718
|
|
|
9367
9719
|
// ../../b4m-core/packages/services/dist/src/llm/tools/implementation/fileRead/index.js
|
|
9368
|
-
import { promises as
|
|
9369
|
-
import { existsSync as
|
|
9370
|
-
import
|
|
9371
|
-
var
|
|
9720
|
+
import { promises as fs8 } from "fs";
|
|
9721
|
+
import { existsSync as existsSync5, statSync as statSync4 } from "fs";
|
|
9722
|
+
import path8 from "path";
|
|
9723
|
+
var MAX_FILE_SIZE3 = 10 * 1024 * 1024;
|
|
9372
9724
|
async function readFileContent(params) {
|
|
9373
9725
|
const { path: filePath, encoding = "utf-8", offset = 0, limit } = params;
|
|
9374
|
-
const normalizedPath =
|
|
9375
|
-
const resolvedPath =
|
|
9376
|
-
const cwd =
|
|
9726
|
+
const normalizedPath = path8.normalize(filePath);
|
|
9727
|
+
const resolvedPath = path8.resolve(process.cwd(), normalizedPath);
|
|
9728
|
+
const cwd = path8.resolve(process.cwd());
|
|
9377
9729
|
if (!resolvedPath.startsWith(cwd)) {
|
|
9378
9730
|
throw new Error(`Access denied: Cannot read files outside of current working directory`);
|
|
9379
9731
|
}
|
|
9380
|
-
if (!
|
|
9732
|
+
if (!existsSync5(resolvedPath)) {
|
|
9381
9733
|
throw new Error(`File not found: ${filePath}`);
|
|
9382
9734
|
}
|
|
9383
9735
|
const stats = statSync4(resolvedPath);
|
|
9384
9736
|
if (stats.isDirectory()) {
|
|
9385
9737
|
throw new Error(`Path is a directory, not a file: ${filePath}`);
|
|
9386
9738
|
}
|
|
9387
|
-
if (stats.size >
|
|
9388
|
-
throw new Error(`File too large: ${(stats.size / 1024 / 1024).toFixed(2)}MB (max ${
|
|
9739
|
+
if (stats.size > MAX_FILE_SIZE3) {
|
|
9740
|
+
throw new Error(`File too large: ${(stats.size / 1024 / 1024).toFixed(2)}MB (max ${MAX_FILE_SIZE3 / 1024 / 1024}MB)`);
|
|
9389
9741
|
}
|
|
9390
9742
|
const isBinary = await checkIfBinary(resolvedPath);
|
|
9391
9743
|
if (isBinary && encoding === "utf-8") {
|
|
9392
9744
|
throw new Error(`File appears to be binary. Use encoding 'base64' to read binary files, or specify a different encoding.`);
|
|
9393
9745
|
}
|
|
9394
|
-
const content = await
|
|
9746
|
+
const content = await fs8.readFile(resolvedPath, encoding);
|
|
9395
9747
|
if (typeof content === "string") {
|
|
9396
9748
|
const lines = content.split("\n");
|
|
9397
9749
|
const totalLines = lines.length;
|
|
@@ -9429,7 +9781,7 @@ ${content}`;
|
|
|
9429
9781
|
}
|
|
9430
9782
|
async function checkIfBinary(filePath) {
|
|
9431
9783
|
const buffer = Buffer.alloc(8192);
|
|
9432
|
-
const fd = await
|
|
9784
|
+
const fd = await fs8.open(filePath, "r");
|
|
9433
9785
|
try {
|
|
9434
9786
|
const { bytesRead } = await fd.read(buffer, 0, 8192, 0);
|
|
9435
9787
|
const chunk = buffer.slice(0, bytesRead);
|
|
@@ -9446,7 +9798,7 @@ var fileReadTool = {
|
|
|
9446
9798
|
context.logger.info("\u{1F4C4} FileRead: Reading file", { path: params.path });
|
|
9447
9799
|
try {
|
|
9448
9800
|
const content = await readFileContent(params);
|
|
9449
|
-
const stats = statSync4(
|
|
9801
|
+
const stats = statSync4(path8.resolve(process.cwd(), path8.normalize(params.path)));
|
|
9450
9802
|
context.logger.info("\u2705 FileRead: Success", {
|
|
9451
9803
|
path: params.path,
|
|
9452
9804
|
size: stats.size,
|
|
@@ -9490,25 +9842,25 @@ var fileReadTool = {
|
|
|
9490
9842
|
};
|
|
9491
9843
|
|
|
9492
9844
|
// ../../b4m-core/packages/services/dist/src/llm/tools/implementation/createFile/index.js
|
|
9493
|
-
import { promises as
|
|
9494
|
-
import { existsSync as
|
|
9495
|
-
import
|
|
9845
|
+
import { promises as fs9 } from "fs";
|
|
9846
|
+
import { existsSync as existsSync6 } from "fs";
|
|
9847
|
+
import path9 from "path";
|
|
9496
9848
|
async function createFile(params) {
|
|
9497
9849
|
const { path: filePath, content, createDirectories = true } = params;
|
|
9498
|
-
const normalizedPath =
|
|
9499
|
-
const resolvedPath =
|
|
9500
|
-
const cwd =
|
|
9850
|
+
const normalizedPath = path9.normalize(filePath);
|
|
9851
|
+
const resolvedPath = path9.resolve(process.cwd(), normalizedPath);
|
|
9852
|
+
const cwd = path9.resolve(process.cwd());
|
|
9501
9853
|
if (!resolvedPath.startsWith(cwd)) {
|
|
9502
9854
|
throw new Error(`Access denied: Cannot create files outside of current working directory`);
|
|
9503
9855
|
}
|
|
9504
|
-
const fileExists =
|
|
9856
|
+
const fileExists = existsSync6(resolvedPath);
|
|
9505
9857
|
const action = fileExists ? "overwritten" : "created";
|
|
9506
9858
|
if (createDirectories) {
|
|
9507
|
-
const dir =
|
|
9508
|
-
await
|
|
9859
|
+
const dir = path9.dirname(resolvedPath);
|
|
9860
|
+
await fs9.mkdir(dir, { recursive: true });
|
|
9509
9861
|
}
|
|
9510
|
-
await
|
|
9511
|
-
const stats = await
|
|
9862
|
+
await fs9.writeFile(resolvedPath, content, "utf-8");
|
|
9863
|
+
const stats = await fs9.stat(resolvedPath);
|
|
9512
9864
|
const lines = content.split("\n").length;
|
|
9513
9865
|
return `File ${action} successfully: ${filePath}
|
|
9514
9866
|
Size: ${stats.size} bytes
|
|
@@ -9519,7 +9871,7 @@ var createFileTool = {
|
|
|
9519
9871
|
implementation: (context) => ({
|
|
9520
9872
|
toolFn: async (value) => {
|
|
9521
9873
|
const params = value;
|
|
9522
|
-
const fileExists =
|
|
9874
|
+
const fileExists = existsSync6(path9.resolve(process.cwd(), path9.normalize(params.path)));
|
|
9523
9875
|
context.logger.info(`\u{1F4DD} CreateFile: ${fileExists ? "Overwriting" : "Creating"} file`, {
|
|
9524
9876
|
path: params.path,
|
|
9525
9877
|
size: params.content.length
|
|
@@ -9561,7 +9913,7 @@ var createFileTool = {
|
|
|
9561
9913
|
// ../../b4m-core/packages/services/dist/src/llm/tools/implementation/globFiles/index.js
|
|
9562
9914
|
import { glob } from "glob";
|
|
9563
9915
|
import { stat } from "fs/promises";
|
|
9564
|
-
import
|
|
9916
|
+
import path10 from "path";
|
|
9565
9917
|
var DEFAULT_IGNORE_PATTERNS = [
|
|
9566
9918
|
"**/node_modules/**",
|
|
9567
9919
|
"**/.git/**",
|
|
@@ -9577,7 +9929,7 @@ var DEFAULT_IGNORE_PATTERNS = [
|
|
|
9577
9929
|
async function findFiles(params) {
|
|
9578
9930
|
const { pattern, dir_path, case_sensitive = true, respect_git_ignore = true } = params;
|
|
9579
9931
|
const baseCwd = process.cwd();
|
|
9580
|
-
const targetDir = dir_path ?
|
|
9932
|
+
const targetDir = dir_path ? path10.resolve(baseCwd, path10.normalize(dir_path)) : baseCwd;
|
|
9581
9933
|
if (!targetDir.startsWith(baseCwd)) {
|
|
9582
9934
|
throw new Error(`Access denied: Cannot search outside of current working directory`);
|
|
9583
9935
|
}
|
|
@@ -9617,7 +9969,7 @@ async function findFiles(params) {
|
|
|
9617
9969
|
const summary = `Found ${filesWithStats.length} file(s)${truncated ? ` (showing first ${MAX_RESULTS})` : ""} matching: ${pattern}`;
|
|
9618
9970
|
const dirInfo = dir_path ? `
|
|
9619
9971
|
Directory: ${dir_path}` : "";
|
|
9620
|
-
const filesList = results.map((file) =>
|
|
9972
|
+
const filesList = results.map((file) => path10.relative(baseCwd, file.path)).join("\n");
|
|
9621
9973
|
return `${summary}${dirInfo}
|
|
9622
9974
|
|
|
9623
9975
|
${filesList}`;
|
|
@@ -9671,27 +10023,48 @@ var globFilesTool = {
|
|
|
9671
10023
|
|
|
9672
10024
|
// ../../b4m-core/packages/services/dist/src/llm/tools/implementation/grepSearch/index.js
|
|
9673
10025
|
import { stat as stat2 } from "fs/promises";
|
|
9674
|
-
import
|
|
9675
|
-
import
|
|
10026
|
+
import { existsSync as existsSync7 } from "fs";
|
|
10027
|
+
import path11 from "path";
|
|
10028
|
+
import { execFile, execFileSync as execFileSync2 } from "child_process";
|
|
9676
10029
|
import { promisify } from "util";
|
|
9677
10030
|
import { createRequire } from "module";
|
|
9678
10031
|
var execFileAsync = promisify(execFile);
|
|
9679
10032
|
var require2 = createRequire(import.meta.url);
|
|
10033
|
+
var cachedRgPath = null;
|
|
9680
10034
|
function getRipgrepPath() {
|
|
10035
|
+
if (cachedRgPath)
|
|
10036
|
+
return cachedRgPath;
|
|
9681
10037
|
try {
|
|
9682
10038
|
const ripgrepPath = require2.resolve("@vscode/ripgrep");
|
|
9683
|
-
const ripgrepDir =
|
|
9684
|
-
const rgBinary =
|
|
10039
|
+
const ripgrepDir = path11.dirname(ripgrepPath);
|
|
10040
|
+
const rgBinary = path11.join(ripgrepDir, "..", "bin", "rg" + (process.platform === "win32" ? ".exe" : ""));
|
|
10041
|
+
if (!existsSync7(rgBinary)) {
|
|
10042
|
+
const postinstallScript = path11.join(ripgrepDir, "..", "lib", "postinstall.js");
|
|
10043
|
+
if (existsSync7(postinstallScript)) {
|
|
10044
|
+
execFileSync2(process.execPath, [postinstallScript], {
|
|
10045
|
+
cwd: path11.join(ripgrepDir, ".."),
|
|
10046
|
+
stdio: "pipe",
|
|
10047
|
+
timeout: 3e4
|
|
10048
|
+
});
|
|
10049
|
+
}
|
|
10050
|
+
if (!existsSync7(rgBinary)) {
|
|
10051
|
+
throw new Error(`ripgrep binary not found at ${rgBinary}. The postinstall download may have failed. Try running: cd ${path11.join(ripgrepDir, "..")} && node lib/postinstall.js`);
|
|
10052
|
+
}
|
|
10053
|
+
}
|
|
10054
|
+
cachedRgPath = rgBinary;
|
|
9685
10055
|
return rgBinary;
|
|
9686
10056
|
} catch (error) {
|
|
10057
|
+
if (error instanceof Error && error.message.includes("ripgrep binary not found")) {
|
|
10058
|
+
throw error;
|
|
10059
|
+
}
|
|
9687
10060
|
throw new Error("ripgrep is not available. Please install @vscode/ripgrep: pnpm add @vscode/ripgrep --filter @bike4mind/services");
|
|
9688
10061
|
}
|
|
9689
10062
|
}
|
|
9690
10063
|
function isPathWithinWorkspace(targetPath, baseCwd) {
|
|
9691
|
-
const resolvedTarget =
|
|
9692
|
-
const resolvedBase =
|
|
9693
|
-
const relativePath =
|
|
9694
|
-
return !relativePath.startsWith("..") && !
|
|
10064
|
+
const resolvedTarget = path11.resolve(targetPath);
|
|
10065
|
+
const resolvedBase = path11.resolve(baseCwd);
|
|
10066
|
+
const relativePath = path11.relative(resolvedBase, resolvedTarget);
|
|
10067
|
+
return !relativePath.startsWith("..") && !path11.isAbsolute(relativePath);
|
|
9695
10068
|
}
|
|
9696
10069
|
function convertGlobToRipgrepGlobs(globPattern) {
|
|
9697
10070
|
if (!globPattern || globPattern === "**/*") {
|
|
@@ -9707,7 +10080,7 @@ function convertGlobToRipgrepGlobs(globPattern) {
|
|
|
9707
10080
|
async function searchFiles2(params) {
|
|
9708
10081
|
const { pattern, dir_path, include } = params;
|
|
9709
10082
|
const baseCwd = process.cwd();
|
|
9710
|
-
const targetDir = dir_path ?
|
|
10083
|
+
const targetDir = dir_path ? path11.resolve(baseCwd, dir_path) : baseCwd;
|
|
9711
10084
|
if (!isPathWithinWorkspace(targetDir, baseCwd)) {
|
|
9712
10085
|
throw new Error(`Path validation failed: "${dir_path}" resolves outside the allowed workspace directory`);
|
|
9713
10086
|
}
|
|
@@ -9769,7 +10142,7 @@ async function searchFiles2(params) {
|
|
|
9769
10142
|
if (item.type === "match") {
|
|
9770
10143
|
const match = item;
|
|
9771
10144
|
allMatches.push({
|
|
9772
|
-
filePath:
|
|
10145
|
+
filePath: path11.relative(targetDir, match.data.path.text) || path11.basename(match.data.path.text),
|
|
9773
10146
|
lineNumber: match.data.line_number,
|
|
9774
10147
|
line: match.data.lines.text.trimEnd()
|
|
9775
10148
|
// Remove trailing newline
|
|
@@ -9862,18 +10235,18 @@ var grepSearchTool = {
|
|
|
9862
10235
|
};
|
|
9863
10236
|
|
|
9864
10237
|
// ../../b4m-core/packages/services/dist/src/llm/tools/implementation/deleteFile/index.js
|
|
9865
|
-
import { promises as
|
|
9866
|
-
import { existsSync as
|
|
9867
|
-
import
|
|
10238
|
+
import { promises as fs10 } from "fs";
|
|
10239
|
+
import { existsSync as existsSync8, statSync as statSync5 } from "fs";
|
|
10240
|
+
import path12 from "path";
|
|
9868
10241
|
async function deleteFile(params) {
|
|
9869
10242
|
const { path: filePath, recursive = false } = params;
|
|
9870
|
-
const normalizedPath =
|
|
9871
|
-
const resolvedPath =
|
|
9872
|
-
const cwd =
|
|
10243
|
+
const normalizedPath = path12.normalize(filePath);
|
|
10244
|
+
const resolvedPath = path12.resolve(process.cwd(), normalizedPath);
|
|
10245
|
+
const cwd = path12.resolve(process.cwd());
|
|
9873
10246
|
if (!resolvedPath.startsWith(cwd)) {
|
|
9874
10247
|
throw new Error(`Access denied: Cannot delete files outside of current working directory`);
|
|
9875
10248
|
}
|
|
9876
|
-
if (!
|
|
10249
|
+
if (!existsSync8(resolvedPath)) {
|
|
9877
10250
|
throw new Error(`File or directory not found: ${filePath}`);
|
|
9878
10251
|
}
|
|
9879
10252
|
const stats = statSync5(resolvedPath);
|
|
@@ -9883,10 +10256,10 @@ async function deleteFile(params) {
|
|
|
9883
10256
|
throw new Error(`Path is a directory: ${filePath}. Use recursive=true to delete directories and their contents.`);
|
|
9884
10257
|
}
|
|
9885
10258
|
if (isDirectory) {
|
|
9886
|
-
await
|
|
10259
|
+
await fs10.rm(resolvedPath, { recursive: true, force: true });
|
|
9887
10260
|
return `Directory deleted successfully: ${filePath}`;
|
|
9888
10261
|
} else {
|
|
9889
|
-
await
|
|
10262
|
+
await fs10.unlink(resolvedPath);
|
|
9890
10263
|
return `File deleted successfully: ${filePath}
|
|
9891
10264
|
Size: ${size} bytes`;
|
|
9892
10265
|
}
|
|
@@ -9896,8 +10269,8 @@ var deleteFileTool = {
|
|
|
9896
10269
|
implementation: (context) => ({
|
|
9897
10270
|
toolFn: async (value) => {
|
|
9898
10271
|
const params = value;
|
|
9899
|
-
const resolvedPath =
|
|
9900
|
-
const isDirectory =
|
|
10272
|
+
const resolvedPath = path12.resolve(process.cwd(), path12.normalize(params.path));
|
|
10273
|
+
const isDirectory = existsSync8(resolvedPath) && statSync5(resolvedPath).isDirectory();
|
|
9901
10274
|
context.logger.info(`\u{1F5D1}\uFE0F DeleteFile: Deleting ${isDirectory ? "directory" : "file"}`, {
|
|
9902
10275
|
path: params.path,
|
|
9903
10276
|
recursive: params.recursive
|
|
@@ -9942,13 +10315,13 @@ function formatSearchResults(files) {
|
|
|
9942
10315
|
const notes = file.notes ? `
|
|
9943
10316
|
Notes: ${file.notes}` : "";
|
|
9944
10317
|
const fileType = file.type || "FILE";
|
|
9945
|
-
return `${index + 1}. **${file.fileName}**
|
|
10318
|
+
return `${index + 1}. **${file.fileName}** (ID: ${file.id})
|
|
9946
10319
|
Type: ${fileType} | MIME: ${file.mimeType}
|
|
9947
10320
|
Tags: ${tags}${notes}`;
|
|
9948
10321
|
});
|
|
9949
10322
|
return `Found ${files.length} document(s) in your knowledge base:
|
|
9950
10323
|
|
|
9951
|
-
` + formattedFiles.join("\n\n") + "\n\n*
|
|
10324
|
+
` + formattedFiles.join("\n\n") + "\n\n*Use retrieve_knowledge_content with a file ID or tags to read the actual document content.*";
|
|
9952
10325
|
}
|
|
9953
10326
|
var knowledgeBaseSearchTool = {
|
|
9954
10327
|
name: "search_knowledge_base",
|
|
@@ -9975,6 +10348,8 @@ var knowledgeBaseSearchTool = {
|
|
|
9975
10348
|
by: "fileName",
|
|
9976
10349
|
direction: "asc"
|
|
9977
10350
|
}, {
|
|
10351
|
+
textSearch: true,
|
|
10352
|
+
// Search across fileName + tags + notes for better recall
|
|
9978
10353
|
includeShared: true,
|
|
9979
10354
|
// Include owned + explicitly shared + org-shared files
|
|
9980
10355
|
userGroups: context.user.groups || []
|
|
@@ -9989,18 +10364,18 @@ var knowledgeBaseSearchTool = {
|
|
|
9989
10364
|
},
|
|
9990
10365
|
toolSchema: {
|
|
9991
10366
|
name: "search_knowledge_base",
|
|
9992
|
-
description: "Search the user's uploaded knowledge base (fab files). Returns relevant documents from the user's own files, organization-shared files, and files explicitly shared with them. Use this tool when the user asks about their own documents, uploaded files, or organization knowledge.",
|
|
10367
|
+
description: "Search the user's uploaded knowledge base (fab files). Searches across file names, tags, and notes for broad recall. Returns relevant documents from the user's own files, organization-shared files, and files explicitly shared with them. Use this tool when the user asks about their own documents, uploaded files, or organization knowledge.",
|
|
9993
10368
|
parameters: {
|
|
9994
10369
|
type: "object",
|
|
9995
10370
|
properties: {
|
|
9996
10371
|
query: {
|
|
9997
10372
|
type: "string",
|
|
9998
|
-
description: "The search query to find relevant documents
|
|
10373
|
+
description: "The search query to find relevant documents. Matches against file names, tags, and notes."
|
|
9999
10374
|
},
|
|
10000
10375
|
tags: {
|
|
10001
10376
|
type: "array",
|
|
10002
10377
|
items: { type: "string" },
|
|
10003
|
-
description:
|
|
10378
|
+
description: 'Optional: filter results by tag names. Supports partial matching. For optimization docs, use tags like "opti:family:scheduling", "opti:QUBO", "opti:solver:highs", etc. Any matching tag qualifies the file.'
|
|
10004
10379
|
},
|
|
10005
10380
|
file_type: {
|
|
10006
10381
|
type: "string",
|
|
@@ -10020,85 +10395,1707 @@ var knowledgeBaseSearchTool = {
|
|
|
10020
10395
|
})
|
|
10021
10396
|
};
|
|
10022
10397
|
|
|
10023
|
-
// ../../b4m-core/packages/services/dist/src/llm/tools/implementation/
|
|
10024
|
-
|
|
10025
|
-
|
|
10026
|
-
var
|
|
10027
|
-
|
|
10028
|
-
|
|
10029
|
-
|
|
10030
|
-
|
|
10031
|
-
|
|
10032
|
-
|
|
10033
|
-
|
|
10034
|
-
|
|
10035
|
-
|
|
10036
|
-
|
|
10037
|
-
|
|
10038
|
-
|
|
10039
|
-
|
|
10040
|
-
|
|
10041
|
-
|
|
10042
|
-
|
|
10043
|
-
|
|
10044
|
-
|
|
10045
|
-
|
|
10046
|
-
|
|
10047
|
-
|
|
10048
|
-
|
|
10049
|
-
|
|
10050
|
-
|
|
10051
|
-
|
|
10052
|
-
|
|
10053
|
-
|
|
10054
|
-
|
|
10055
|
-
|
|
10056
|
-
|
|
10057
|
-
|
|
10058
|
-
|
|
10059
|
-
|
|
10060
|
-
|
|
10061
|
-
|
|
10062
|
-
|
|
10063
|
-
|
|
10064
|
-
|
|
10065
|
-
|
|
10066
|
-
|
|
10067
|
-
|
|
10068
|
-
|
|
10069
|
-
|
|
10070
|
-
|
|
10071
|
-
|
|
10072
|
-
|
|
10073
|
-
|
|
10074
|
-
|
|
10075
|
-
|
|
10076
|
-
|
|
10077
|
-
|
|
10078
|
-
|
|
10079
|
-
|
|
10080
|
-
|
|
10081
|
-
|
|
10082
|
-
|
|
10083
|
-
|
|
10084
|
-
|
|
10085
|
-
|
|
10086
|
-
|
|
10087
|
-
|
|
10088
|
-
|
|
10089
|
-
|
|
10090
|
-
|
|
10091
|
-
|
|
10092
|
-
|
|
10093
|
-
|
|
10094
|
-
|
|
10095
|
-
|
|
10096
|
-
|
|
10097
|
-
|
|
10098
|
-
|
|
10099
|
-
|
|
10100
|
-
|
|
10101
|
-
|
|
10398
|
+
// ../../b4m-core/packages/services/dist/src/llm/tools/implementation/knowledgeBaseRetrieve/index.js
|
|
10399
|
+
var DEFAULT_MAX_CHARS = 8e3;
|
|
10400
|
+
var ABSOLUTE_MAX_CHARS = 16e3;
|
|
10401
|
+
var knowledgeBaseRetrieveTool = {
|
|
10402
|
+
name: "retrieve_knowledge_content",
|
|
10403
|
+
implementation: (context) => ({
|
|
10404
|
+
toolFn: async (value) => {
|
|
10405
|
+
const params = value;
|
|
10406
|
+
const { file_id, tags, query, max_chars } = params;
|
|
10407
|
+
const charBudget = Math.min(max_chars ?? DEFAULT_MAX_CHARS, ABSOLUTE_MAX_CHARS);
|
|
10408
|
+
context.logger.log("\u{1F4D6} Knowledge Retrieve: params", { file_id, tags, query, max_chars: charBudget });
|
|
10409
|
+
if (!file_id && !tags?.length && !query) {
|
|
10410
|
+
return "Error: You must provide at least one of file_id, tags, or query.";
|
|
10411
|
+
}
|
|
10412
|
+
if (!context.db.fabfiles) {
|
|
10413
|
+
context.logger.error("\u274C Knowledge Retrieve: fabfiles repository not available");
|
|
10414
|
+
return "Knowledge base retrieval is not available at this time.";
|
|
10415
|
+
}
|
|
10416
|
+
if (!context.db.fabfilechunks) {
|
|
10417
|
+
context.logger.error("\u274C Knowledge Retrieve: fabfilechunks repository not available");
|
|
10418
|
+
return "Knowledge base retrieval is not available at this time (chunk reader unavailable).";
|
|
10419
|
+
}
|
|
10420
|
+
try {
|
|
10421
|
+
let files = [];
|
|
10422
|
+
if (file_id) {
|
|
10423
|
+
const file = await context.db.fabfiles.findByIdAndUserId(file_id, context.userId);
|
|
10424
|
+
if (file) {
|
|
10425
|
+
files = [file];
|
|
10426
|
+
} else {
|
|
10427
|
+
const searchResults = await context.db.fabfiles.search(context.userId, file_id, { tags: [], shared: false }, { page: 1, limit: 1 }, { by: "fileName", direction: "asc" }, { textSearch: true, includeShared: true, userGroups: context.user.groups || [] });
|
|
10428
|
+
files = searchResults.data;
|
|
10429
|
+
}
|
|
10430
|
+
if (files.length === 0) {
|
|
10431
|
+
return `No document found with ID "${file_id}". The file may not exist or you may not have access to it. Try using search_knowledge_base to find the correct file ID.`;
|
|
10432
|
+
}
|
|
10433
|
+
}
|
|
10434
|
+
if (files.length === 0 && (tags?.length || query)) {
|
|
10435
|
+
const searchResults = await context.db.fabfiles.search(context.userId, query || "", { tags: tags || [], shared: false }, { page: 1, limit: 5 }, { by: "fileName", direction: "asc" }, { textSearch: true, includeShared: true, userGroups: context.user.groups || [] });
|
|
10436
|
+
files = searchResults.data;
|
|
10437
|
+
if (files.length === 0) {
|
|
10438
|
+
const searchDesc = [query && `query "${query}"`, tags?.length && `tags [${tags.join(", ")}]`].filter(Boolean).join(" and ");
|
|
10439
|
+
return `No documents found matching ${searchDesc}. Try broadening your search with search_knowledge_base.`;
|
|
10440
|
+
}
|
|
10441
|
+
}
|
|
10442
|
+
let totalCharsUsed = 0;
|
|
10443
|
+
const sections = [];
|
|
10444
|
+
let filesRetrieved = 0;
|
|
10445
|
+
for (const file of files) {
|
|
10446
|
+
if (totalCharsUsed >= charBudget)
|
|
10447
|
+
break;
|
|
10448
|
+
const chunks = await context.db.fabfilechunks.findByFabFileId(file.id);
|
|
10449
|
+
if (chunks.length === 0) {
|
|
10450
|
+
context.logger.log(`\u{1F4D6} Knowledge Retrieve: No chunks for file ${file.fileName} (${file.id})`);
|
|
10451
|
+
continue;
|
|
10452
|
+
}
|
|
10453
|
+
const fullText = chunks.map((c) => c.text).join("\n");
|
|
10454
|
+
const remainingBudget = charBudget - totalCharsUsed;
|
|
10455
|
+
const truncated = fullText.length > remainingBudget;
|
|
10456
|
+
const content = truncated ? fullText.slice(0, remainingBudget) : fullText;
|
|
10457
|
+
const fileTags = file.tags?.map((t) => t.name).join(", ") || "none";
|
|
10458
|
+
const charLabel = truncated ? `${content.length} (truncated from ${fullText.length})` : `${content.length}`;
|
|
10459
|
+
sections.push(`### ${file.fileName} (ID: ${file.id})
|
|
10460
|
+
Tags: ${fileTags}
|
|
10461
|
+
Chunks: ${chunks.length} | Characters: ${charLabel}
|
|
10462
|
+
---
|
|
10463
|
+
` + content);
|
|
10464
|
+
totalCharsUsed += content.length;
|
|
10465
|
+
filesRetrieved++;
|
|
10466
|
+
}
|
|
10467
|
+
if (filesRetrieved === 0) {
|
|
10468
|
+
return "Found matching documents but they have no indexed content. The files may not have been processed yet.";
|
|
10469
|
+
}
|
|
10470
|
+
const header = `Retrieved content from ${filesRetrieved} of ${files.length} document(s):
|
|
10471
|
+
`;
|
|
10472
|
+
return header + "\n" + sections.join("\n\n---\n\n");
|
|
10473
|
+
} catch (error) {
|
|
10474
|
+
context.logger.error("\u274C Knowledge Retrieve: Error during retrieval:", error);
|
|
10475
|
+
return "An error occurred while retrieving document content. Please try again.";
|
|
10476
|
+
}
|
|
10477
|
+
},
|
|
10478
|
+
toolSchema: {
|
|
10479
|
+
name: "retrieve_knowledge_content",
|
|
10480
|
+
description: "Read the actual text content of knowledge base documents. Use this after search_knowledge_base to read documents by file ID, or provide tags/query to find and read documents in one step. Returns the full text content (up to the character budget) for grounding your responses in the user's curated knowledge.",
|
|
10481
|
+
parameters: {
|
|
10482
|
+
type: "object",
|
|
10483
|
+
properties: {
|
|
10484
|
+
file_id: {
|
|
10485
|
+
type: "string",
|
|
10486
|
+
description: "The file ID to retrieve (from search_knowledge_base results). Most efficient for single-document retrieval."
|
|
10487
|
+
},
|
|
10488
|
+
tags: {
|
|
10489
|
+
type: "array",
|
|
10490
|
+
items: { type: "string" },
|
|
10491
|
+
description: 'Filter documents by tags. For optimization docs, use tags like "opti:family:scheduling", "opti:solver:highs", etc.'
|
|
10492
|
+
},
|
|
10493
|
+
query: {
|
|
10494
|
+
type: "string",
|
|
10495
|
+
description: "Search query to find documents. Can be combined with tags for more targeted retrieval."
|
|
10496
|
+
},
|
|
10497
|
+
max_chars: {
|
|
10498
|
+
type: "number",
|
|
10499
|
+
description: "Maximum characters of content to return (default: 8000, max: 16000). Lower values for quick lookups, higher for detailed reading.",
|
|
10500
|
+
minimum: 500,
|
|
10501
|
+
maximum: 16e3
|
|
10502
|
+
}
|
|
10503
|
+
}
|
|
10504
|
+
}
|
|
10505
|
+
}
|
|
10506
|
+
})
|
|
10507
|
+
};
|
|
10508
|
+
|
|
10509
|
+
// ../../b4m-core/packages/quantum/dist/src/types.js
|
|
10510
|
+
import { z as z139 } from "zod";
|
|
10511
|
+
var OperationSchema = z139.object({
|
|
10512
|
+
jobId: z139.number(),
|
|
10513
|
+
machineId: z139.number(),
|
|
10514
|
+
duration: z139.number().positive()
|
|
10515
|
+
});
|
|
10516
|
+
var JobSchema = z139.object({
|
|
10517
|
+
id: z139.number(),
|
|
10518
|
+
name: z139.string(),
|
|
10519
|
+
operations: z139.array(OperationSchema)
|
|
10520
|
+
});
|
|
10521
|
+
var MachineSchema = z139.object({
|
|
10522
|
+
id: z139.number(),
|
|
10523
|
+
name: z139.string()
|
|
10524
|
+
});
|
|
10525
|
+
var SchedulingProblemSchema = z139.object({
|
|
10526
|
+
name: z139.string(),
|
|
10527
|
+
description: z139.string().optional(),
|
|
10528
|
+
jobs: z139.array(JobSchema),
|
|
10529
|
+
machines: z139.array(MachineSchema)
|
|
10530
|
+
});
|
|
10531
|
+
var SolverIdSchema = z139.enum([
|
|
10532
|
+
"greedy",
|
|
10533
|
+
"naive",
|
|
10534
|
+
"random-restart",
|
|
10535
|
+
"simulated-annealing",
|
|
10536
|
+
"simulated-annealing-medium",
|
|
10537
|
+
"simulated-annealing-large",
|
|
10538
|
+
"tabu",
|
|
10539
|
+
"genetic-algorithm",
|
|
10540
|
+
"ant-colony",
|
|
10541
|
+
"highs",
|
|
10542
|
+
"simulated-qaoa",
|
|
10543
|
+
"ionq-qaoa"
|
|
10544
|
+
]);
|
|
10545
|
+
|
|
10546
|
+
// ../../b4m-core/packages/quantum/dist/src/solvers/greedy-solver.js
|
|
10547
|
+
var greedySolver = {
|
|
10548
|
+
id: "greedy",
|
|
10549
|
+
name: "Greedy (Earliest Start)",
|
|
10550
|
+
description: "Schedules each operation at the earliest available time on its machine, respecting job ordering.",
|
|
10551
|
+
async solve(problem, options) {
|
|
10552
|
+
const startTime = performance.now();
|
|
10553
|
+
const machineAvailableAt = /* @__PURE__ */ new Map();
|
|
10554
|
+
for (const machine of problem.machines) {
|
|
10555
|
+
machineAvailableAt.set(machine.id, 0);
|
|
10556
|
+
}
|
|
10557
|
+
const jobCompletedAt = /* @__PURE__ */ new Map();
|
|
10558
|
+
for (const job of problem.jobs) {
|
|
10559
|
+
jobCompletedAt.set(job.id, 0);
|
|
10560
|
+
}
|
|
10561
|
+
const schedule = [];
|
|
10562
|
+
const totalOps = problem.jobs.reduce((sum2, job) => sum2 + job.operations.length, 0);
|
|
10563
|
+
let opsScheduled = 0;
|
|
10564
|
+
for (const job of problem.jobs) {
|
|
10565
|
+
for (let opIndex = 0; opIndex < job.operations.length; opIndex++) {
|
|
10566
|
+
const op = job.operations[opIndex];
|
|
10567
|
+
const machineReady = machineAvailableAt.get(op.machineId) ?? 0;
|
|
10568
|
+
const jobReady = jobCompletedAt.get(job.id) ?? 0;
|
|
10569
|
+
const opStart = Math.max(machineReady, jobReady);
|
|
10570
|
+
const opEnd = opStart + op.duration;
|
|
10571
|
+
schedule.push({
|
|
10572
|
+
jobId: job.id,
|
|
10573
|
+
machineId: op.machineId,
|
|
10574
|
+
operationIndex: opIndex,
|
|
10575
|
+
startTime: opStart,
|
|
10576
|
+
endTime: opEnd
|
|
10577
|
+
});
|
|
10578
|
+
machineAvailableAt.set(op.machineId, opEnd);
|
|
10579
|
+
jobCompletedAt.set(job.id, opEnd);
|
|
10580
|
+
opsScheduled++;
|
|
10581
|
+
if (options?.onProgress) {
|
|
10582
|
+
options.onProgress({
|
|
10583
|
+
solverId: "greedy",
|
|
10584
|
+
percentage: Math.round(opsScheduled / totalOps * 100),
|
|
10585
|
+
bestMakespan: opEnd,
|
|
10586
|
+
currentIteration: opsScheduled,
|
|
10587
|
+
totalIterations: totalOps
|
|
10588
|
+
});
|
|
10589
|
+
}
|
|
10590
|
+
}
|
|
10591
|
+
}
|
|
10592
|
+
const makespan = Math.max(...schedule.map((op) => op.endTime));
|
|
10593
|
+
const elapsedMs = performance.now() - startTime;
|
|
10594
|
+
return {
|
|
10595
|
+
solverId: "greedy",
|
|
10596
|
+
solverName: "Greedy (Earliest Start)",
|
|
10597
|
+
makespan,
|
|
10598
|
+
schedule,
|
|
10599
|
+
elapsedMs,
|
|
10600
|
+
iterations: totalOps
|
|
10601
|
+
};
|
|
10602
|
+
}
|
|
10603
|
+
};
|
|
10604
|
+
|
|
10605
|
+
// ../../b4m-core/packages/quantum/dist/src/solvers/schedule-utils.js
|
|
10606
|
+
function buildOperationList(problem) {
|
|
10607
|
+
const ops = [];
|
|
10608
|
+
for (const job of problem.jobs) {
|
|
10609
|
+
for (let i = 0; i < job.operations.length; i++) {
|
|
10610
|
+
ops.push({ jobId: job.id, opIndex: i });
|
|
10611
|
+
}
|
|
10612
|
+
}
|
|
10613
|
+
return ops;
|
|
10614
|
+
}
|
|
10615
|
+
function decodePermutation(permutation, problem) {
|
|
10616
|
+
const schedule = [];
|
|
10617
|
+
const machineAvailableAt = /* @__PURE__ */ new Map();
|
|
10618
|
+
const jobCompletedAt = /* @__PURE__ */ new Map();
|
|
10619
|
+
const jobNextOp = /* @__PURE__ */ new Map();
|
|
10620
|
+
for (const machine of problem.machines) {
|
|
10621
|
+
machineAvailableAt.set(machine.id, 0);
|
|
10622
|
+
}
|
|
10623
|
+
for (const job of problem.jobs) {
|
|
10624
|
+
jobCompletedAt.set(job.id, 0);
|
|
10625
|
+
jobNextOp.set(job.id, 0);
|
|
10626
|
+
}
|
|
10627
|
+
const remaining = [...permutation];
|
|
10628
|
+
const maxPasses = remaining.length * remaining.length;
|
|
10629
|
+
let passes = 0;
|
|
10630
|
+
while (remaining.length > 0 && passes < maxPasses) {
|
|
10631
|
+
let scheduled = false;
|
|
10632
|
+
for (let i = 0; i < remaining.length; i++) {
|
|
10633
|
+
const ref = remaining[i];
|
|
10634
|
+
const nextOp = jobNextOp.get(ref.jobId) ?? 0;
|
|
10635
|
+
if (ref.opIndex !== nextOp)
|
|
10636
|
+
continue;
|
|
10637
|
+
const job = problem.jobs.find((j) => j.id === ref.jobId);
|
|
10638
|
+
const op = job.operations[ref.opIndex];
|
|
10639
|
+
const machineReady = machineAvailableAt.get(op.machineId) ?? 0;
|
|
10640
|
+
const jobReady = jobCompletedAt.get(ref.jobId) ?? 0;
|
|
10641
|
+
const start = Math.max(machineReady, jobReady);
|
|
10642
|
+
const end = start + op.duration;
|
|
10643
|
+
schedule.push({
|
|
10644
|
+
jobId: ref.jobId,
|
|
10645
|
+
machineId: op.machineId,
|
|
10646
|
+
operationIndex: ref.opIndex,
|
|
10647
|
+
startTime: start,
|
|
10648
|
+
endTime: end
|
|
10649
|
+
});
|
|
10650
|
+
machineAvailableAt.set(op.machineId, end);
|
|
10651
|
+
jobCompletedAt.set(ref.jobId, end);
|
|
10652
|
+
jobNextOp.set(ref.jobId, nextOp + 1);
|
|
10653
|
+
remaining.splice(i, 1);
|
|
10654
|
+
scheduled = true;
|
|
10655
|
+
break;
|
|
10656
|
+
}
|
|
10657
|
+
if (!scheduled) {
|
|
10658
|
+
remaining.push(remaining.shift());
|
|
10659
|
+
}
|
|
10660
|
+
passes++;
|
|
10661
|
+
}
|
|
10662
|
+
return schedule;
|
|
10663
|
+
}
|
|
10664
|
+
function computeMakespan(schedule) {
|
|
10665
|
+
if (schedule.length === 0)
|
|
10666
|
+
return 0;
|
|
10667
|
+
return Math.max(...schedule.map((op) => op.endTime));
|
|
10668
|
+
}
|
|
10669
|
+
function randomPermutation(problem, rng = Math.random) {
|
|
10670
|
+
const ops = buildOperationList(problem);
|
|
10671
|
+
for (let i = ops.length - 1; i > 0; i--) {
|
|
10672
|
+
const j = Math.floor(rng() * (i + 1));
|
|
10673
|
+
[ops[i], ops[j]] = [ops[j], ops[i]];
|
|
10674
|
+
}
|
|
10675
|
+
return ops;
|
|
10676
|
+
}
|
|
10677
|
+
function swapNeighbor(perm, rng = Math.random) {
|
|
10678
|
+
const newPerm = [...perm];
|
|
10679
|
+
const i = Math.floor(rng() * newPerm.length);
|
|
10680
|
+
let j = Math.floor(rng() * (newPerm.length - 1));
|
|
10681
|
+
if (j >= i)
|
|
10682
|
+
j++;
|
|
10683
|
+
[newPerm[i], newPerm[j]] = [newPerm[j], newPerm[i]];
|
|
10684
|
+
return newPerm;
|
|
10685
|
+
}
|
|
10686
|
+
function createRng(seed) {
|
|
10687
|
+
let s = seed | 0;
|
|
10688
|
+
return () => {
|
|
10689
|
+
s = s + 1831565813 | 0;
|
|
10690
|
+
let t = Math.imul(s ^ s >>> 15, 1 | s);
|
|
10691
|
+
t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t;
|
|
10692
|
+
return ((t ^ t >>> 14) >>> 0) / 4294967296;
|
|
10693
|
+
};
|
|
10694
|
+
}
|
|
10695
|
+
|
|
10696
|
+
// ../../b4m-core/packages/quantum/dist/src/solvers/naive-solver.js
|
|
10697
|
+
var MAX_PERMUTATIONS = 40320;
|
|
10698
|
+
function* permutations(arr) {
|
|
10699
|
+
if (arr.length <= 1) {
|
|
10700
|
+
yield [...arr];
|
|
10701
|
+
return;
|
|
10702
|
+
}
|
|
10703
|
+
for (let i = 0; i < arr.length; i++) {
|
|
10704
|
+
const rest = [...arr.slice(0, i), ...arr.slice(i + 1)];
|
|
10705
|
+
for (const perm of permutations(rest)) {
|
|
10706
|
+
yield [arr[i], ...perm];
|
|
10707
|
+
}
|
|
10708
|
+
}
|
|
10709
|
+
}
|
|
10710
|
+
var naiveSolver = {
|
|
10711
|
+
id: "naive",
|
|
10712
|
+
name: "Naive (Brute Force)",
|
|
10713
|
+
description: "Evaluates all permutations to find the optimal schedule. Only feasible for small problems.",
|
|
10714
|
+
async solve(problem, options) {
|
|
10715
|
+
const startTime = performance.now();
|
|
10716
|
+
const ops = buildOperationList(problem);
|
|
10717
|
+
const totalPerms = factorial(ops.length);
|
|
10718
|
+
const cappedTotal = Math.min(totalPerms, MAX_PERMUTATIONS);
|
|
10719
|
+
let bestMakespan = Infinity;
|
|
10720
|
+
let bestSchedule = decodePermutation(ops, problem);
|
|
10721
|
+
let count = 0;
|
|
10722
|
+
for (const perm of permutations(ops)) {
|
|
10723
|
+
if (count >= MAX_PERMUTATIONS)
|
|
10724
|
+
break;
|
|
10725
|
+
const schedule = decodePermutation(perm, problem);
|
|
10726
|
+
const makespan = computeMakespan(schedule);
|
|
10727
|
+
if (makespan < bestMakespan) {
|
|
10728
|
+
bestMakespan = makespan;
|
|
10729
|
+
bestSchedule = schedule;
|
|
10730
|
+
}
|
|
10731
|
+
count++;
|
|
10732
|
+
if (options?.onProgress && count % 100 === 0) {
|
|
10733
|
+
options.onProgress({
|
|
10734
|
+
solverId: "naive",
|
|
10735
|
+
percentage: Math.min(99, Math.round(count / cappedTotal * 100)),
|
|
10736
|
+
bestMakespan,
|
|
10737
|
+
currentIteration: count,
|
|
10738
|
+
totalIterations: cappedTotal
|
|
10739
|
+
});
|
|
10740
|
+
}
|
|
10741
|
+
}
|
|
10742
|
+
options?.onProgress?.({
|
|
10743
|
+
solverId: "naive",
|
|
10744
|
+
percentage: 100,
|
|
10745
|
+
bestMakespan,
|
|
10746
|
+
currentIteration: count,
|
|
10747
|
+
totalIterations: cappedTotal
|
|
10748
|
+
});
|
|
10749
|
+
return {
|
|
10750
|
+
solverId: "naive",
|
|
10751
|
+
solverName: "Naive (Brute Force)",
|
|
10752
|
+
makespan: bestMakespan,
|
|
10753
|
+
schedule: bestSchedule,
|
|
10754
|
+
elapsedMs: performance.now() - startTime,
|
|
10755
|
+
iterations: count
|
|
10756
|
+
};
|
|
10757
|
+
}
|
|
10758
|
+
};
|
|
10759
|
+
function factorial(n) {
|
|
10760
|
+
if (n <= 1)
|
|
10761
|
+
return 1;
|
|
10762
|
+
let result = 1;
|
|
10763
|
+
for (let i = 2; i <= n; i++)
|
|
10764
|
+
result *= i;
|
|
10765
|
+
return result;
|
|
10766
|
+
}
|
|
10767
|
+
|
|
10768
|
+
// ../../b4m-core/packages/quantum/dist/src/solvers/random-restart-solver.js
|
|
10769
|
+
var RESTARTS = 50;
|
|
10770
|
+
var HILL_CLIMB_ITERATIONS = 500;
|
|
10771
|
+
var randomRestartSolver = {
|
|
10772
|
+
id: "random-restart",
|
|
10773
|
+
name: "Random Restart Hill Climbing",
|
|
10774
|
+
description: "Multiple random starts with local search improvement via operation swaps.",
|
|
10775
|
+
async solve(problem, options) {
|
|
10776
|
+
const startTime = performance.now();
|
|
10777
|
+
const timeoutMs = options?.timeoutMs ?? 1e4;
|
|
10778
|
+
const rng = createRng(42);
|
|
10779
|
+
let globalBestMakespan = Infinity;
|
|
10780
|
+
let globalBestSchedule = decodePermutation(randomPermutation(problem, rng), problem);
|
|
10781
|
+
let totalIterations = 0;
|
|
10782
|
+
for (let restart = 0; restart < RESTARTS; restart++) {
|
|
10783
|
+
if (performance.now() - startTime > timeoutMs)
|
|
10784
|
+
break;
|
|
10785
|
+
let currentPerm = randomPermutation(problem, rng);
|
|
10786
|
+
let currentSchedule = decodePermutation(currentPerm, problem);
|
|
10787
|
+
let currentMakespan = computeMakespan(currentSchedule);
|
|
10788
|
+
for (let i = 0; i < HILL_CLIMB_ITERATIONS; i++) {
|
|
10789
|
+
if (performance.now() - startTime > timeoutMs)
|
|
10790
|
+
break;
|
|
10791
|
+
const neighborPerm = swapNeighbor(currentPerm, rng);
|
|
10792
|
+
const neighborSchedule = decodePermutation(neighborPerm, problem);
|
|
10793
|
+
const neighborMakespan = computeMakespan(neighborSchedule);
|
|
10794
|
+
if (neighborMakespan < currentMakespan) {
|
|
10795
|
+
currentPerm = neighborPerm;
|
|
10796
|
+
currentSchedule = neighborSchedule;
|
|
10797
|
+
currentMakespan = neighborMakespan;
|
|
10798
|
+
}
|
|
10799
|
+
totalIterations++;
|
|
10800
|
+
}
|
|
10801
|
+
if (currentMakespan < globalBestMakespan) {
|
|
10802
|
+
globalBestMakespan = currentMakespan;
|
|
10803
|
+
globalBestSchedule = currentSchedule;
|
|
10804
|
+
}
|
|
10805
|
+
options?.onProgress?.({
|
|
10806
|
+
solverId: "random-restart",
|
|
10807
|
+
percentage: Math.round((restart + 1) / RESTARTS * 100),
|
|
10808
|
+
bestMakespan: globalBestMakespan,
|
|
10809
|
+
currentIteration: restart + 1,
|
|
10810
|
+
totalIterations: RESTARTS
|
|
10811
|
+
});
|
|
10812
|
+
}
|
|
10813
|
+
return {
|
|
10814
|
+
solverId: "random-restart",
|
|
10815
|
+
solverName: "Random Restart Hill Climbing",
|
|
10816
|
+
makespan: globalBestMakespan,
|
|
10817
|
+
schedule: globalBestSchedule,
|
|
10818
|
+
elapsedMs: performance.now() - startTime,
|
|
10819
|
+
iterations: totalIterations
|
|
10820
|
+
};
|
|
10821
|
+
}
|
|
10822
|
+
};
|
|
10823
|
+
|
|
10824
|
+
// ../../b4m-core/packages/quantum/dist/src/solvers/simulated-annealing-solver.js
|
|
10825
|
+
var SA_CONFIGS = [
|
|
10826
|
+
{
|
|
10827
|
+
id: "simulated-annealing",
|
|
10828
|
+
name: "Simulated Annealing (S)",
|
|
10829
|
+
description: "Fast simulated annealing with 5,000 iterations. Good for quick estimates.",
|
|
10830
|
+
initialTemp: 100,
|
|
10831
|
+
coolingRate: 0.995,
|
|
10832
|
+
iterations: 5e3
|
|
10833
|
+
},
|
|
10834
|
+
{
|
|
10835
|
+
id: "simulated-annealing-medium",
|
|
10836
|
+
name: "Simulated Annealing (M)",
|
|
10837
|
+
description: "Medium simulated annealing with 25,000 iterations. Balanced speed/quality.",
|
|
10838
|
+
initialTemp: 200,
|
|
10839
|
+
coolingRate: 0.9985,
|
|
10840
|
+
iterations: 25e3
|
|
10841
|
+
},
|
|
10842
|
+
{
|
|
10843
|
+
id: "simulated-annealing-large",
|
|
10844
|
+
name: "Simulated Annealing (L)",
|
|
10845
|
+
description: "Long simulated annealing with 100,000 iterations. Best solution quality.",
|
|
10846
|
+
initialTemp: 500,
|
|
10847
|
+
coolingRate: 0.99995,
|
|
10848
|
+
iterations: 1e5
|
|
10849
|
+
}
|
|
10850
|
+
];
|
|
10851
|
+
function createSASolver(config) {
|
|
10852
|
+
return {
|
|
10853
|
+
id: config.id,
|
|
10854
|
+
name: config.name,
|
|
10855
|
+
description: config.description,
|
|
10856
|
+
async solve(problem, options) {
|
|
10857
|
+
const startTime = performance.now();
|
|
10858
|
+
const timeoutMs = options?.timeoutMs ?? 3e4;
|
|
10859
|
+
const rng = createRng(42);
|
|
10860
|
+
let currentPerm = randomPermutation(problem, rng);
|
|
10861
|
+
let currentSchedule = decodePermutation(currentPerm, problem);
|
|
10862
|
+
let currentMakespan = computeMakespan(currentSchedule);
|
|
10863
|
+
let bestSchedule = currentSchedule;
|
|
10864
|
+
let bestMakespan = currentMakespan;
|
|
10865
|
+
let temperature = config.initialTemp;
|
|
10866
|
+
const progressInterval = Math.max(1, Math.floor(config.iterations / 100));
|
|
10867
|
+
for (let i = 0; i < config.iterations; i++) {
|
|
10868
|
+
if (performance.now() - startTime > timeoutMs)
|
|
10869
|
+
break;
|
|
10870
|
+
const neighborPerm = swapNeighbor(currentPerm, rng);
|
|
10871
|
+
const neighborSchedule = decodePermutation(neighborPerm, problem);
|
|
10872
|
+
const neighborMakespan = computeMakespan(neighborSchedule);
|
|
10873
|
+
const delta = neighborMakespan - currentMakespan;
|
|
10874
|
+
if (delta < 0 || rng() < Math.exp(-delta / temperature)) {
|
|
10875
|
+
currentPerm = neighborPerm;
|
|
10876
|
+
currentSchedule = neighborSchedule;
|
|
10877
|
+
currentMakespan = neighborMakespan;
|
|
10878
|
+
if (currentMakespan < bestMakespan) {
|
|
10879
|
+
bestSchedule = currentSchedule;
|
|
10880
|
+
bestMakespan = currentMakespan;
|
|
10881
|
+
}
|
|
10882
|
+
}
|
|
10883
|
+
temperature *= config.coolingRate;
|
|
10884
|
+
if (options?.onProgress && i % progressInterval === 0) {
|
|
10885
|
+
options.onProgress({
|
|
10886
|
+
solverId: config.id,
|
|
10887
|
+
percentage: Math.round(i / config.iterations * 100),
|
|
10888
|
+
bestMakespan,
|
|
10889
|
+
currentIteration: i,
|
|
10890
|
+
totalIterations: config.iterations
|
|
10891
|
+
});
|
|
10892
|
+
}
|
|
10893
|
+
}
|
|
10894
|
+
options?.onProgress?.({
|
|
10895
|
+
solverId: config.id,
|
|
10896
|
+
percentage: 100,
|
|
10897
|
+
bestMakespan,
|
|
10898
|
+
currentIteration: config.iterations,
|
|
10899
|
+
totalIterations: config.iterations
|
|
10900
|
+
});
|
|
10901
|
+
return {
|
|
10902
|
+
solverId: config.id,
|
|
10903
|
+
solverName: config.name,
|
|
10904
|
+
makespan: bestMakespan,
|
|
10905
|
+
schedule: bestSchedule,
|
|
10906
|
+
elapsedMs: performance.now() - startTime,
|
|
10907
|
+
iterations: config.iterations
|
|
10908
|
+
};
|
|
10909
|
+
}
|
|
10910
|
+
};
|
|
10911
|
+
}
|
|
10912
|
+
var simulatedAnnealingSolver = createSASolver(SA_CONFIGS[0]);
|
|
10913
|
+
var simulatedAnnealingMediumSolver = createSASolver(SA_CONFIGS[1]);
|
|
10914
|
+
var simulatedAnnealingLargeSolver = createSASolver(SA_CONFIGS[2]);
|
|
10915
|
+
|
|
10916
|
+
// ../../b4m-core/packages/quantum/dist/src/solvers/tabu-solver.js
|
|
10917
|
+
var ITERATIONS = 1e4;
|
|
10918
|
+
var TABU_TENURE = 15;
|
|
10919
|
+
var NEIGHBORHOOD_SIZE = 20;
|
|
10920
|
+
var tabuSolver = {
|
|
10921
|
+
id: "tabu",
|
|
10922
|
+
name: "Tabu Search",
|
|
10923
|
+
description: "Hill climbing with short-term memory to avoid revisiting recent solutions.",
|
|
10924
|
+
async solve(problem, options) {
|
|
10925
|
+
const startTime = performance.now();
|
|
10926
|
+
const timeoutMs = options?.timeoutMs ?? 15e3;
|
|
10927
|
+
const rng = createRng(42);
|
|
10928
|
+
let currentPerm = randomPermutation(problem, rng);
|
|
10929
|
+
let currentSchedule = decodePermutation(currentPerm, problem);
|
|
10930
|
+
let currentMakespan = computeMakespan(currentSchedule);
|
|
10931
|
+
let bestSchedule = currentSchedule;
|
|
10932
|
+
let bestMakespan = currentMakespan;
|
|
10933
|
+
const tabuList = /* @__PURE__ */ new Map();
|
|
10934
|
+
const progressInterval = Math.max(1, Math.floor(ITERATIONS / 100));
|
|
10935
|
+
for (let iter = 0; iter < ITERATIONS; iter++) {
|
|
10936
|
+
if (performance.now() - startTime > timeoutMs)
|
|
10937
|
+
break;
|
|
10938
|
+
let bestNeighborPerm = null;
|
|
10939
|
+
let bestNeighborMakespan = Infinity;
|
|
10940
|
+
let bestSwapKey = "";
|
|
10941
|
+
for (let n = 0; n < NEIGHBORHOOD_SIZE; n++) {
|
|
10942
|
+
const i = Math.floor(rng() * currentPerm.length);
|
|
10943
|
+
let j = Math.floor(rng() * (currentPerm.length - 1));
|
|
10944
|
+
if (j >= i)
|
|
10945
|
+
j++;
|
|
10946
|
+
const swapKey = `${Math.min(i, j)},${Math.max(i, j)}`;
|
|
10947
|
+
const candidatePerm = [...currentPerm];
|
|
10948
|
+
[candidatePerm[i], candidatePerm[j]] = [candidatePerm[j], candidatePerm[i]];
|
|
10949
|
+
const candidateSchedule = decodePermutation(candidatePerm, problem);
|
|
10950
|
+
const candidateMakespan = computeMakespan(candidateSchedule);
|
|
10951
|
+
const isTabu = (tabuList.get(swapKey) ?? 0) > iter;
|
|
10952
|
+
const aspirationMet = candidateMakespan < bestMakespan;
|
|
10953
|
+
if ((!isTabu || aspirationMet) && candidateMakespan < bestNeighborMakespan) {
|
|
10954
|
+
bestNeighborPerm = candidatePerm;
|
|
10955
|
+
bestNeighborMakespan = candidateMakespan;
|
|
10956
|
+
bestSwapKey = swapKey;
|
|
10957
|
+
}
|
|
10958
|
+
}
|
|
10959
|
+
if (bestNeighborPerm) {
|
|
10960
|
+
currentPerm = bestNeighborPerm;
|
|
10961
|
+
currentSchedule = decodePermutation(currentPerm, problem);
|
|
10962
|
+
currentMakespan = bestNeighborMakespan;
|
|
10963
|
+
tabuList.set(bestSwapKey, iter + TABU_TENURE);
|
|
10964
|
+
if (currentMakespan < bestMakespan) {
|
|
10965
|
+
bestMakespan = currentMakespan;
|
|
10966
|
+
bestSchedule = currentSchedule;
|
|
10967
|
+
}
|
|
10968
|
+
}
|
|
10969
|
+
if (iter % 100 === 0) {
|
|
10970
|
+
for (const [key, expiry] of tabuList) {
|
|
10971
|
+
if (expiry <= iter)
|
|
10972
|
+
tabuList.delete(key);
|
|
10973
|
+
}
|
|
10974
|
+
}
|
|
10975
|
+
if (options?.onProgress && iter % progressInterval === 0) {
|
|
10976
|
+
options.onProgress({
|
|
10977
|
+
solverId: "tabu",
|
|
10978
|
+
percentage: Math.round(iter / ITERATIONS * 100),
|
|
10979
|
+
bestMakespan,
|
|
10980
|
+
currentIteration: iter,
|
|
10981
|
+
totalIterations: ITERATIONS
|
|
10982
|
+
});
|
|
10983
|
+
}
|
|
10984
|
+
}
|
|
10985
|
+
options?.onProgress?.({
|
|
10986
|
+
solverId: "tabu",
|
|
10987
|
+
percentage: 100,
|
|
10988
|
+
bestMakespan,
|
|
10989
|
+
currentIteration: ITERATIONS,
|
|
10990
|
+
totalIterations: ITERATIONS
|
|
10991
|
+
});
|
|
10992
|
+
return {
|
|
10993
|
+
solverId: "tabu",
|
|
10994
|
+
solverName: "Tabu Search",
|
|
10995
|
+
makespan: bestMakespan,
|
|
10996
|
+
schedule: bestSchedule,
|
|
10997
|
+
elapsedMs: performance.now() - startTime,
|
|
10998
|
+
iterations: ITERATIONS
|
|
10999
|
+
};
|
|
11000
|
+
}
|
|
11001
|
+
};
|
|
11002
|
+
|
|
11003
|
+
// ../../b4m-core/packages/quantum/dist/src/solvers/genetic-algorithm-solver.js
|
|
11004
|
+
var POPULATION_SIZE = 50;
|
|
11005
|
+
var GENERATIONS = 200;
|
|
11006
|
+
var TOURNAMENT_SIZE = 5;
|
|
11007
|
+
var MUTATION_RATE = 0.15;
|
|
11008
|
+
var geneticAlgorithmSolver = {
|
|
11009
|
+
id: "genetic-algorithm",
|
|
11010
|
+
name: "Genetic Algorithm",
|
|
11011
|
+
description: "Population-based search with tournament selection, crossover, and mutation.",
|
|
11012
|
+
async solve(problem, options) {
|
|
11013
|
+
const startTime = performance.now();
|
|
11014
|
+
const timeoutMs = options?.timeoutMs ?? 2e4;
|
|
11015
|
+
const rng = createRng(42);
|
|
11016
|
+
let population = [];
|
|
11017
|
+
for (let i = 0; i < POPULATION_SIZE; i++) {
|
|
11018
|
+
const perm = randomPermutation(problem, rng);
|
|
11019
|
+
const schedule = decodePermutation(perm, problem);
|
|
11020
|
+
const makespan = computeMakespan(schedule);
|
|
11021
|
+
population.push({ perm, makespan });
|
|
11022
|
+
}
|
|
11023
|
+
let bestIndividual = population.reduce((best, ind) => ind.makespan < best.makespan ? ind : best);
|
|
11024
|
+
for (let gen = 0; gen < GENERATIONS; gen++) {
|
|
11025
|
+
if (performance.now() - startTime > timeoutMs)
|
|
11026
|
+
break;
|
|
11027
|
+
const newPopulation = [];
|
|
11028
|
+
newPopulation.push({ ...bestIndividual });
|
|
11029
|
+
while (newPopulation.length < POPULATION_SIZE) {
|
|
11030
|
+
const parent1 = tournamentSelect(population, TOURNAMENT_SIZE, rng);
|
|
11031
|
+
const parent2 = tournamentSelect(population, TOURNAMENT_SIZE, rng);
|
|
11032
|
+
let childPerm = crossover(parent1.perm, parent2.perm, rng);
|
|
11033
|
+
if (rng() < MUTATION_RATE) {
|
|
11034
|
+
childPerm = swapNeighbor(childPerm, rng);
|
|
11035
|
+
}
|
|
11036
|
+
const childSchedule = decodePermutation(childPerm, problem);
|
|
11037
|
+
const childMakespan = computeMakespan(childSchedule);
|
|
11038
|
+
newPopulation.push({ perm: childPerm, makespan: childMakespan });
|
|
11039
|
+
}
|
|
11040
|
+
population = newPopulation;
|
|
11041
|
+
const genBest = population.reduce((best, ind) => ind.makespan < best.makespan ? ind : best);
|
|
11042
|
+
if (genBest.makespan < bestIndividual.makespan) {
|
|
11043
|
+
bestIndividual = genBest;
|
|
11044
|
+
}
|
|
11045
|
+
options?.onProgress?.({
|
|
11046
|
+
solverId: "genetic-algorithm",
|
|
11047
|
+
percentage: Math.round((gen + 1) / GENERATIONS * 100),
|
|
11048
|
+
bestMakespan: bestIndividual.makespan,
|
|
11049
|
+
currentIteration: gen + 1,
|
|
11050
|
+
totalIterations: GENERATIONS
|
|
11051
|
+
});
|
|
11052
|
+
}
|
|
11053
|
+
const bestSchedule = decodePermutation(bestIndividual.perm, problem);
|
|
11054
|
+
return {
|
|
11055
|
+
solverId: "genetic-algorithm",
|
|
11056
|
+
solverName: "Genetic Algorithm",
|
|
11057
|
+
makespan: bestIndividual.makespan,
|
|
11058
|
+
schedule: bestSchedule,
|
|
11059
|
+
elapsedMs: performance.now() - startTime,
|
|
11060
|
+
iterations: GENERATIONS * POPULATION_SIZE
|
|
11061
|
+
};
|
|
11062
|
+
}
|
|
11063
|
+
};
|
|
11064
|
+
function tournamentSelect(population, size, rng) {
|
|
11065
|
+
let best = population[Math.floor(rng() * population.length)];
|
|
11066
|
+
for (let i = 1; i < size; i++) {
|
|
11067
|
+
const candidate = population[Math.floor(rng() * population.length)];
|
|
11068
|
+
if (candidate.makespan < best.makespan) {
|
|
11069
|
+
best = candidate;
|
|
11070
|
+
}
|
|
11071
|
+
}
|
|
11072
|
+
return best;
|
|
11073
|
+
}
|
|
11074
|
+
function crossover(parent1, parent2, rng) {
|
|
11075
|
+
const len = parent1.length;
|
|
11076
|
+
const start = Math.floor(rng() * len);
|
|
11077
|
+
const end = start + Math.floor(rng() * (len - start));
|
|
11078
|
+
const child = new Array(len).fill(null);
|
|
11079
|
+
const used = /* @__PURE__ */ new Set();
|
|
11080
|
+
for (let i = start; i <= end && i < len; i++) {
|
|
11081
|
+
child[i] = parent1[i];
|
|
11082
|
+
used.add(`${parent1[i].jobId}-${parent1[i].opIndex}`);
|
|
11083
|
+
}
|
|
11084
|
+
let pos = 0;
|
|
11085
|
+
for (const op of parent2) {
|
|
11086
|
+
const key = `${op.jobId}-${op.opIndex}`;
|
|
11087
|
+
if (!used.has(key)) {
|
|
11088
|
+
while (child[pos] !== null)
|
|
11089
|
+
pos++;
|
|
11090
|
+
child[pos] = op;
|
|
11091
|
+
used.add(key);
|
|
11092
|
+
}
|
|
11093
|
+
}
|
|
11094
|
+
return child;
|
|
11095
|
+
}
|
|
11096
|
+
|
|
11097
|
+
// ../../b4m-core/packages/quantum/dist/src/solvers/ant-colony-solver.js
|
|
11098
|
+
var NUM_ANTS = 20;
|
|
11099
|
+
var ITERATIONS2 = 100;
|
|
11100
|
+
var ALPHA = 1;
|
|
11101
|
+
var BETA = 2;
|
|
11102
|
+
var EVAPORATION = 0.1;
|
|
11103
|
+
var Q = 100;
|
|
11104
|
+
var antColonySolver = {
|
|
11105
|
+
id: "ant-colony",
|
|
11106
|
+
name: "Ant Colony Optimization",
|
|
11107
|
+
description: "Swarm intelligence using pheromone trails to guide solution construction.",
|
|
11108
|
+
async solve(problem, options) {
|
|
11109
|
+
const startTime = performance.now();
|
|
11110
|
+
const timeoutMs = options?.timeoutMs ?? 2e4;
|
|
11111
|
+
const rng = createRng(42);
|
|
11112
|
+
const allOps = buildOperationList(problem);
|
|
11113
|
+
const numOps = allOps.length;
|
|
11114
|
+
const pheromone = Array.from({ length: numOps }, () => new Array(numOps).fill(1));
|
|
11115
|
+
const heuristic = allOps.map((ref) => {
|
|
11116
|
+
const job = problem.jobs.find((j) => j.id === ref.jobId);
|
|
11117
|
+
const op = job.operations[ref.opIndex];
|
|
11118
|
+
return 1 / op.duration;
|
|
11119
|
+
});
|
|
11120
|
+
let bestMakespan = Infinity;
|
|
11121
|
+
let bestSchedule = decodePermutation(allOps, problem);
|
|
11122
|
+
for (let iter = 0; iter < ITERATIONS2; iter++) {
|
|
11123
|
+
if (performance.now() - startTime > timeoutMs)
|
|
11124
|
+
break;
|
|
11125
|
+
const antSolutions = [];
|
|
11126
|
+
for (let ant = 0; ant < NUM_ANTS; ant++) {
|
|
11127
|
+
const perm = constructSolution(allOps, pheromone, heuristic, rng);
|
|
11128
|
+
const schedule = decodePermutation(perm, problem);
|
|
11129
|
+
const makespan = computeMakespan(schedule);
|
|
11130
|
+
antSolutions.push({ perm, makespan });
|
|
11131
|
+
if (makespan < bestMakespan) {
|
|
11132
|
+
bestMakespan = makespan;
|
|
11133
|
+
bestSchedule = schedule;
|
|
11134
|
+
}
|
|
11135
|
+
}
|
|
11136
|
+
for (let i = 0; i < numOps; i++) {
|
|
11137
|
+
for (let j = 0; j < numOps; j++) {
|
|
11138
|
+
pheromone[i][j] *= 1 - EVAPORATION;
|
|
11139
|
+
}
|
|
11140
|
+
}
|
|
11141
|
+
for (const solution of antSolutions) {
|
|
11142
|
+
const deposit = Q / solution.makespan;
|
|
11143
|
+
for (let pos = 0; pos < solution.perm.length; pos++) {
|
|
11144
|
+
const opIdx = allOps.findIndex((o) => o.jobId === solution.perm[pos].jobId && o.opIndex === solution.perm[pos].opIndex);
|
|
11145
|
+
if (opIdx >= 0) {
|
|
11146
|
+
pheromone[pos][opIdx] += deposit;
|
|
11147
|
+
}
|
|
11148
|
+
}
|
|
11149
|
+
}
|
|
11150
|
+
options?.onProgress?.({
|
|
11151
|
+
solverId: "ant-colony",
|
|
11152
|
+
percentage: Math.round((iter + 1) / ITERATIONS2 * 100),
|
|
11153
|
+
bestMakespan,
|
|
11154
|
+
currentIteration: iter + 1,
|
|
11155
|
+
totalIterations: ITERATIONS2
|
|
11156
|
+
});
|
|
11157
|
+
}
|
|
11158
|
+
return {
|
|
11159
|
+
solverId: "ant-colony",
|
|
11160
|
+
solverName: "Ant Colony Optimization",
|
|
11161
|
+
makespan: bestMakespan,
|
|
11162
|
+
schedule: bestSchedule,
|
|
11163
|
+
elapsedMs: performance.now() - startTime,
|
|
11164
|
+
iterations: ITERATIONS2 * NUM_ANTS
|
|
11165
|
+
};
|
|
11166
|
+
}
|
|
11167
|
+
};
|
|
11168
|
+
function constructSolution(allOps, pheromone, heuristic, rng) {
|
|
11169
|
+
const remaining = new Set(allOps.map((_, i) => i));
|
|
11170
|
+
const result = [];
|
|
11171
|
+
for (let pos = 0; pos < allOps.length; pos++) {
|
|
11172
|
+
const candidates = Array.from(remaining);
|
|
11173
|
+
const weights = candidates.map((opIdx) => {
|
|
11174
|
+
const tau = Math.pow(pheromone[pos][opIdx], ALPHA);
|
|
11175
|
+
const eta = Math.pow(heuristic[opIdx], BETA);
|
|
11176
|
+
return tau * eta;
|
|
11177
|
+
});
|
|
11178
|
+
const totalWeight = weights.reduce((sum2, w) => sum2 + w, 0);
|
|
11179
|
+
let r = rng() * totalWeight;
|
|
11180
|
+
let selectedIdx = candidates[0];
|
|
11181
|
+
for (let i = 0; i < candidates.length; i++) {
|
|
11182
|
+
r -= weights[i];
|
|
11183
|
+
if (r <= 0) {
|
|
11184
|
+
selectedIdx = candidates[i];
|
|
11185
|
+
break;
|
|
11186
|
+
}
|
|
11187
|
+
}
|
|
11188
|
+
result.push(allOps[selectedIdx]);
|
|
11189
|
+
remaining.delete(selectedIdx);
|
|
11190
|
+
}
|
|
11191
|
+
return result;
|
|
11192
|
+
}
|
|
11193
|
+
|
|
11194
|
+
// ../../b4m-core/packages/quantum/dist/src/solvers/metadata.js
|
|
11195
|
+
var solverMetadata = {
|
|
11196
|
+
naive: {
|
|
11197
|
+
icon: "\u{1F40C}",
|
|
11198
|
+
// 🐌
|
|
11199
|
+
color: "neutral",
|
|
11200
|
+
complexity: "Exponential - O(n!)",
|
|
11201
|
+
requires: "Browser compute",
|
|
11202
|
+
tagline: "Try every possible solution",
|
|
11203
|
+
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.",
|
|
11204
|
+
howItWorks: [
|
|
11205
|
+
"Generate all valid operation orderings (permutations)",
|
|
11206
|
+
"Evaluate each ordering to compute makespan",
|
|
11207
|
+
"Track the best solution found",
|
|
11208
|
+
"Return the globally optimal solution"
|
|
11209
|
+
],
|
|
11210
|
+
keyParameters: [{ name: "Search Space", value: "n! permutations for n operations (with pruning for precedence)" }],
|
|
11211
|
+
strengths: ["Guarantees optimal solution", "Simple to understand and implement", "No parameter tuning needed"],
|
|
11212
|
+
weaknesses: [
|
|
11213
|
+
"O(n!) time complexity",
|
|
11214
|
+
"Infeasible for problems with >10 operations",
|
|
11215
|
+
"No early termination heuristics"
|
|
11216
|
+
],
|
|
11217
|
+
bestFor: "Tiny problems (\u226410 ops) where you need a guaranteed optimal baseline",
|
|
11218
|
+
wikipedia: "https://en.wikipedia.org/wiki/Brute-force_search"
|
|
11219
|
+
},
|
|
11220
|
+
greedy: {
|
|
11221
|
+
icon: "\u26A1",
|
|
11222
|
+
// ⚡
|
|
11223
|
+
color: "success",
|
|
11224
|
+
complexity: "O(n\xB2)",
|
|
11225
|
+
requires: "Browser compute",
|
|
11226
|
+
tagline: "Always pick the locally best choice",
|
|
11227
|
+
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.',
|
|
11228
|
+
howItWorks: [
|
|
11229
|
+
"Start with an empty schedule",
|
|
11230
|
+
"Find all operations whose predecessors are complete",
|
|
11231
|
+
"Pick the one with shortest duration (SPT rule)",
|
|
11232
|
+
"Schedule it at the earliest possible time",
|
|
11233
|
+
"Repeat until all operations scheduled"
|
|
11234
|
+
],
|
|
11235
|
+
keyParameters: [{ name: "Priority Rule", value: "SPT (Shortest Processing Time) \u2014 favors quick operations" }],
|
|
11236
|
+
strengths: ["Extremely fast (O(n\xB2))", "Produces valid solutions instantly", "Good baseline for comparison"],
|
|
11237
|
+
weaknesses: ["No backtracking", "Often far from optimal", "Can make locally optimal but globally poor choices"],
|
|
11238
|
+
bestFor: "Quick baseline, real-time scheduling, or as initial solution for metaheuristics",
|
|
11239
|
+
wikipedia: "https://en.wikipedia.org/wiki/Greedy_algorithm"
|
|
11240
|
+
},
|
|
11241
|
+
"random-restart": {
|
|
11242
|
+
icon: "\u{1F3B2}",
|
|
11243
|
+
// 🎲
|
|
11244
|
+
color: "primary",
|
|
11245
|
+
complexity: "~5 seconds",
|
|
11246
|
+
requires: "Browser compute",
|
|
11247
|
+
tagline: "Climb hills, restart when stuck",
|
|
11248
|
+
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.",
|
|
11249
|
+
howItWorks: [
|
|
11250
|
+
"Generate a random valid schedule",
|
|
11251
|
+
"Try swapping pairs of operations",
|
|
11252
|
+
"Accept swaps that improve makespan",
|
|
11253
|
+
"When stuck (no improving swaps), restart from new random solution",
|
|
11254
|
+
"Track best solution across all restarts"
|
|
11255
|
+
],
|
|
11256
|
+
keyParameters: [
|
|
11257
|
+
{ name: "Max Restarts", value: "Number of times to restart from random (100)" },
|
|
11258
|
+
{ name: "Stagnation Limit", value: "Iterations without improvement before restart (1000)" }
|
|
11259
|
+
],
|
|
11260
|
+
strengths: ["Escapes local optima via restarts", "Simple and parallelizable", "Better than pure hill climbing"],
|
|
11261
|
+
weaknesses: [
|
|
11262
|
+
"No information sharing between restarts",
|
|
11263
|
+
"May revisit similar solutions",
|
|
11264
|
+
"Random restarts are uninformed"
|
|
11265
|
+
],
|
|
11266
|
+
bestFor: "Medium problems where pure hill climbing gets stuck",
|
|
11267
|
+
wikipedia: "https://en.wikipedia.org/wiki/Hill_climbing#Random-restart_hill_climbing"
|
|
11268
|
+
},
|
|
11269
|
+
"simulated-annealing": {
|
|
11270
|
+
icon: "\u{1F525}",
|
|
11271
|
+
// 🔥
|
|
11272
|
+
color: "warning",
|
|
11273
|
+
complexity: "~7 seconds",
|
|
11274
|
+
requires: "Browser compute",
|
|
11275
|
+
tagline: "Cool down from chaos to order",
|
|
11276
|
+
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.',
|
|
11277
|
+
howItWorks: [
|
|
11278
|
+
"Start with high temperature (T=100)",
|
|
11279
|
+
"Generate neighbor by swapping two operations",
|
|
11280
|
+
"If better, always accept",
|
|
11281
|
+
"If worse, accept with probability e^(-\u0394/T)",
|
|
11282
|
+
"Gradually reduce temperature (cooling)",
|
|
11283
|
+
"As T\u21920, behaves like pure hill climbing"
|
|
11284
|
+
],
|
|
11285
|
+
keyParameters: [
|
|
11286
|
+
{ name: "Initial Temperature", value: 'Starting "heat" level (100)' },
|
|
11287
|
+
{ name: "Cooling Rate", value: "How fast temperature decreases (~0.9999...)" },
|
|
11288
|
+
{ name: "Iterations", value: "5 million moves evaluated" }
|
|
11289
|
+
],
|
|
11290
|
+
strengths: [
|
|
11291
|
+
"Escapes local optima probabilistically",
|
|
11292
|
+
"Well-studied theoretical properties",
|
|
11293
|
+
"Single parameter to tune (cooling schedule)"
|
|
11294
|
+
],
|
|
11295
|
+
weaknesses: ["Cooling schedule is problem-dependent", "Can be slow to converge", "Single-solution method"],
|
|
11296
|
+
bestFor: "General optimization when you have moderate compute budget",
|
|
11297
|
+
wikipedia: "https://en.wikipedia.org/wiki/Simulated_annealing"
|
|
11298
|
+
},
|
|
11299
|
+
"simulated-annealing-medium": {
|
|
11300
|
+
icon: "\u{1F525}",
|
|
11301
|
+
// 🔥
|
|
11302
|
+
color: "warning",
|
|
11303
|
+
complexity: "~70 seconds",
|
|
11304
|
+
requires: "Browser compute",
|
|
11305
|
+
tagline: "Extended cooling for deeper exploration",
|
|
11306
|
+
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.",
|
|
11307
|
+
howItWorks: ["Same as SA standard", "But with 50 million iterations", "Slower cooling = more exploration time"],
|
|
11308
|
+
keyParameters: [
|
|
11309
|
+
{ name: "Iterations", value: "50 million moves (10\xD7 standard)" },
|
|
11310
|
+
{ name: "Timeout", value: "5 minutes max" }
|
|
11311
|
+
],
|
|
11312
|
+
strengths: ["More thorough than standard SA", "Better for complex landscapes", "Higher chance of global optimum"],
|
|
11313
|
+
weaknesses: [
|
|
11314
|
+
"10\xD7 slower than standard SA",
|
|
11315
|
+
"Diminishing returns on easy problems",
|
|
11316
|
+
"May be overkill for small instances"
|
|
11317
|
+
],
|
|
11318
|
+
bestFor: "When standard SA finds good but not great solutions",
|
|
11319
|
+
wikipedia: "https://en.wikipedia.org/wiki/Simulated_annealing"
|
|
11320
|
+
},
|
|
11321
|
+
"simulated-annealing-large": {
|
|
11322
|
+
icon: "\u{1F525}",
|
|
11323
|
+
// 🔥
|
|
11324
|
+
color: "danger",
|
|
11325
|
+
complexity: "~12 minutes",
|
|
11326
|
+
requires: "Browser compute",
|
|
11327
|
+
tagline: "Maximum exploration budget",
|
|
11328
|
+
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.",
|
|
11329
|
+
howItWorks: [
|
|
11330
|
+
"Same as SA standard/medium",
|
|
11331
|
+
"But with 500 million iterations",
|
|
11332
|
+
"Very slow cooling for maximum exploration"
|
|
11333
|
+
],
|
|
11334
|
+
keyParameters: [
|
|
11335
|
+
{ name: "Iterations", value: "500 million moves (100\xD7 standard)" },
|
|
11336
|
+
{ name: "Timeout", value: "30 minutes max" }
|
|
11337
|
+
],
|
|
11338
|
+
strengths: ["Maximum exploration", "Best chance at global optimum", "Leaves no stone unturned"],
|
|
11339
|
+
weaknesses: ["Very slow (up to 30 min)", "Massive overkill for small problems", "Diminishing returns"],
|
|
11340
|
+
bestFor: "Large problems where quality matters more than time",
|
|
11341
|
+
wikipedia: "https://en.wikipedia.org/wiki/Simulated_annealing"
|
|
11342
|
+
},
|
|
11343
|
+
tabu: {
|
|
11344
|
+
icon: "\u{1F50D}",
|
|
11345
|
+
// 🔍
|
|
11346
|
+
color: "primary",
|
|
11347
|
+
complexity: "~20\u201340 seconds",
|
|
11348
|
+
requires: "Browser compute",
|
|
11349
|
+
tagline: "Remember mistakes to avoid repeating them",
|
|
11350
|
+
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.',
|
|
11351
|
+
howItWorks: [
|
|
11352
|
+
"Start from a random permutation of operations (seed: 42 for reproducibility)",
|
|
11353
|
+
"Generate 20 random neighbor solutions by swapping two operation positions",
|
|
11354
|
+
"Select the best non-tabu neighbor (even if worse than current \u2014 forced exploration)",
|
|
11355
|
+
"Record the swap as tabu for 15 iterations (fixed tenure)",
|
|
11356
|
+
"Aspiration criterion: override tabu if move produces a new global best",
|
|
11357
|
+
"Repeat for 10,000 iterations or until timeout (15s)",
|
|
11358
|
+
"Periodically clean expired entries from the tabu list"
|
|
11359
|
+
],
|
|
11360
|
+
keyParameters: [
|
|
11361
|
+
{ name: "Variant", value: "Simple Tabu Search (Glover 1986) with aspiration by objective" },
|
|
11362
|
+
{ name: "Tabu Structure", value: "Swap-based \u2014 records position pairs (i,j) that were swapped" },
|
|
11363
|
+
{ name: "Tabu Tenure", value: "Fixed at 15 iterations (no reactive adjustment)" },
|
|
11364
|
+
{ name: "Neighborhood Size", value: "20 random swap candidates evaluated per iteration" },
|
|
11365
|
+
{ name: "Total Iterations", value: "10,000" },
|
|
11366
|
+
{ name: "Aspiration", value: "Accept tabu move if it improves the global best" },
|
|
11367
|
+
{ name: "Memory Type", value: "Short-term recency only (no frequency-based long-term memory)" }
|
|
11368
|
+
],
|
|
11369
|
+
strengths: [
|
|
11370
|
+
"Memory prevents cycling back to recently visited solutions",
|
|
11371
|
+
"Forced acceptance of worse moves enables escaping local optima",
|
|
11372
|
+
"Aspiration criterion preserves exploitation of exceptional solutions",
|
|
11373
|
+
"Deterministic with seed (reproducible results)"
|
|
11374
|
+
],
|
|
11375
|
+
weaknesses: [
|
|
11376
|
+
"Fixed tenure \u2014 no adaptive/reactive adjustment to search dynamics",
|
|
11377
|
+
"No long-term memory (frequency-based diversification)",
|
|
11378
|
+
"No intensification phase to deep-search promising regions",
|
|
11379
|
+
"Small neighborhood (20) may miss good moves in large search spaces"
|
|
11380
|
+
],
|
|
11381
|
+
bestFor: "Medium-complexity problems with many local optima and plateau regions",
|
|
11382
|
+
wikipedia: "https://en.wikipedia.org/wiki/Tabu_search",
|
|
11383
|
+
otherResources: [
|
|
11384
|
+
{ label: "Glover (1986) \u2014 Original paper", url: "https://doi.org/10.1016/0305-0548(86)90048-1" },
|
|
11385
|
+
{
|
|
11386
|
+
label: "Glover & Laguna \u2014 Tabu Search (book)",
|
|
11387
|
+
url: "https://en.wikipedia.org/wiki/Tabu_search#References"
|
|
11388
|
+
}
|
|
11389
|
+
]
|
|
11390
|
+
},
|
|
11391
|
+
"genetic-algorithm": {
|
|
11392
|
+
icon: "\u{1F9EC}",
|
|
11393
|
+
// 🧬
|
|
11394
|
+
color: "success",
|
|
11395
|
+
complexity: "~30 seconds",
|
|
11396
|
+
requires: "Browser compute",
|
|
11397
|
+
tagline: "Evolve solutions through natural selection",
|
|
11398
|
+
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.',
|
|
11399
|
+
howItWorks: [
|
|
11400
|
+
"Initialize population of 100 random schedules",
|
|
11401
|
+
"Evaluate fitness (1/makespan) for each",
|
|
11402
|
+
"Selection: pick parents via tournament (best of 5 random)",
|
|
11403
|
+
"Crossover: combine two parents to create child",
|
|
11404
|
+
"Mutation: randomly swap operations (20% chance)",
|
|
11405
|
+
"Elitism: top 5 solutions survive unchanged",
|
|
11406
|
+
"Repeat for 15,000 generations"
|
|
11407
|
+
],
|
|
11408
|
+
keyParameters: [
|
|
11409
|
+
{ name: "Population Size", value: "Number of solutions maintained (100)" },
|
|
11410
|
+
{ name: "Generations", value: "Evolution cycles (15,000)" },
|
|
11411
|
+
{ name: "Crossover Rate", value: "Probability of combining parents (80%)" },
|
|
11412
|
+
{ name: "Mutation Rate", value: "Probability of random change (20%)" },
|
|
11413
|
+
{ name: "Elite Count", value: "Best solutions preserved unchanged (5)" }
|
|
11414
|
+
],
|
|
11415
|
+
strengths: ["Population maintains diversity", "Crossover combines good building blocks", "Naturally parallel"],
|
|
11416
|
+
weaknesses: ["Many parameters to tune", "Can converge prematurely", "Crossover design is problem-specific"],
|
|
11417
|
+
bestFor: "Complex problems where good solutions share common substructures",
|
|
11418
|
+
wikipedia: "https://en.wikipedia.org/wiki/Genetic_algorithm",
|
|
11419
|
+
otherResources: [
|
|
11420
|
+
{
|
|
11421
|
+
label: "Order Crossover (OX)",
|
|
11422
|
+
url: "https://en.wikipedia.org/wiki/Crossover_(genetic_algorithm)#Order_crossover_(OX)"
|
|
11423
|
+
}
|
|
11424
|
+
]
|
|
11425
|
+
},
|
|
11426
|
+
"ant-colony": {
|
|
11427
|
+
icon: "\u{1F41C}",
|
|
11428
|
+
// 🐜
|
|
11429
|
+
color: "success",
|
|
11430
|
+
complexity: "~30 seconds",
|
|
11431
|
+
requires: "Browser compute",
|
|
11432
|
+
tagline: "Follow the pheromone trails",
|
|
11433
|
+
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.",
|
|
11434
|
+
howItWorks: [
|
|
11435
|
+
"Initialize pheromone trails uniformly",
|
|
11436
|
+
"Each ant builds a complete schedule probabilistically",
|
|
11437
|
+
"Operations with more pheromone are more likely to be chosen",
|
|
11438
|
+
"After all ants finish, evaluate their solutions",
|
|
11439
|
+
"Deposit pheromone on edges used by good solutions",
|
|
11440
|
+
"Evaporate some pheromone (forget old information)",
|
|
11441
|
+
"Best ant's solution gets extra pheromone (elitism)"
|
|
11442
|
+
],
|
|
11443
|
+
keyParameters: [
|
|
11444
|
+
{ name: "Number of Ants", value: "Solutions built per iteration (50)" },
|
|
11445
|
+
{ name: "Iterations", value: "Pheromone update cycles (100,000)" },
|
|
11446
|
+
{ name: "Alpha (\u03B1)", value: "Pheromone importance (1.0)" },
|
|
11447
|
+
{ name: "Beta (\u03B2)", value: "Heuristic importance (2.0)" },
|
|
11448
|
+
{ name: "Evaporation Rate", value: "Pheromone decay per iteration (10%)" }
|
|
11449
|
+
],
|
|
11450
|
+
strengths: ["Implicit parallelism", "Positive feedback accelerates convergence", "Robust to problem changes"],
|
|
11451
|
+
weaknesses: ["Many parameters", "Can converge prematurely", "Pheromone model must match problem"],
|
|
11452
|
+
bestFor: 'Routing and sequencing problems with clear "edge" structure',
|
|
11453
|
+
wikipedia: "https://en.wikipedia.org/wiki/Ant_colony_optimization_algorithms",
|
|
11454
|
+
otherResources: [{ label: "Marco Dorigo (inventor)", url: "https://en.wikipedia.org/wiki/Marco_Dorigo" }]
|
|
11455
|
+
},
|
|
11456
|
+
highs: {
|
|
11457
|
+
icon: "\u{1F3AF}",
|
|
11458
|
+
// 🎯
|
|
11459
|
+
color: "success",
|
|
11460
|
+
complexity: "Seconds to minutes",
|
|
11461
|
+
requires: "Browser compute (WASM)",
|
|
11462
|
+
available: false,
|
|
11463
|
+
tagline: "Open-source solver rivaling commercial giants",
|
|
11464
|
+
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.",
|
|
11465
|
+
howItWorks: [
|
|
11466
|
+
"Formulate problem as Integer Linear Program (ILP/MIP)",
|
|
11467
|
+
"Presolve: simplify model, tighten bounds, detect infeasibility",
|
|
11468
|
+
"LP relaxation using dual simplex or interior-point method",
|
|
11469
|
+
"Branch-and-bound with sophisticated node selection",
|
|
11470
|
+
"Cutting planes: Gomory cuts, MIR cuts, cover cuts",
|
|
11471
|
+
"Return optimal (or near-optimal) solution with gap certificate"
|
|
11472
|
+
],
|
|
11473
|
+
keyParameters: [
|
|
11474
|
+
{ name: "Time Limit", value: "Maximum solve time before returning best found" },
|
|
11475
|
+
{ name: "MIP Gap", value: "Stop when proven within X% of optimal (e.g., 0.01 = 1%)" },
|
|
11476
|
+
{ name: "Threads", value: "Number of parallel threads (default: all cores)" }
|
|
11477
|
+
],
|
|
11478
|
+
strengths: [
|
|
11479
|
+
"MIT licensed \u2014 fully open source, embed anywhere",
|
|
11480
|
+
"90%+ of Gurobi/COPT performance on most problems",
|
|
11481
|
+
"Active development with rapid improvements",
|
|
11482
|
+
"Can deploy in customer infrastructure (no license servers)"
|
|
11483
|
+
],
|
|
11484
|
+
weaknesses: [
|
|
11485
|
+
"Slightly slower than Gurobi on very large instances",
|
|
11486
|
+
"Fewer specialized cuts than commercial solvers",
|
|
11487
|
+
"Less mature ecosystem (but growing fast)"
|
|
11488
|
+
],
|
|
11489
|
+
bestFor: "Production scheduling when you need commercial-grade results without commercial licensing",
|
|
11490
|
+
wikipedia: "https://en.wikipedia.org/wiki/HiGHS_optimization_solver",
|
|
11491
|
+
otherResources: [
|
|
11492
|
+
{ label: "HiGHS GitHub", url: "https://github.com/ERGO-Code/HiGHS" },
|
|
11493
|
+
{ label: "HiGHS Documentation", url: "https://highs.dev/" },
|
|
11494
|
+
{ label: "Mittelmann MIP Benchmarks", url: "http://plato.asu.edu/ftp/milp.html" }
|
|
11495
|
+
]
|
|
11496
|
+
},
|
|
11497
|
+
"simulated-qaoa": {
|
|
11498
|
+
icon: "\u{1F52E}",
|
|
11499
|
+
// 🔮
|
|
11500
|
+
color: "primary",
|
|
11501
|
+
complexity: "~30\u201360 seconds",
|
|
11502
|
+
requires: "Backend compute",
|
|
11503
|
+
available: false,
|
|
11504
|
+
tagline: "Quantum-inspired classical simulation",
|
|
11505
|
+
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.",
|
|
11506
|
+
howItWorks: [
|
|
11507
|
+
"Encode scheduling problem as QUBO matrix",
|
|
11508
|
+
"Initialize simulated quantum state |+\u27E9\u2297\u207F",
|
|
11509
|
+
"Apply p layers of QAOA circuit (problem + mixer unitaries)",
|
|
11510
|
+
"Classically optimize variational parameters (\u03B3, \u03B2)",
|
|
11511
|
+
"Measure (sample) the final state to extract candidate schedules",
|
|
11512
|
+
"Decode best bitstring back to a valid schedule"
|
|
11513
|
+
],
|
|
11514
|
+
keyParameters: [
|
|
11515
|
+
{ name: "QAOA Depth (p)", value: "Number of alternating layers (default: 3)" },
|
|
11516
|
+
{ name: "Optimizer", value: "COBYLA for variational parameter optimization" },
|
|
11517
|
+
{ name: "Shots", value: "Number of measurement samples (1024)" },
|
|
11518
|
+
{ name: "Qubits", value: "One per possible (job, timeslot) assignment" }
|
|
11519
|
+
],
|
|
11520
|
+
strengths: [
|
|
11521
|
+
"Preview quantum algorithms without quantum hardware",
|
|
11522
|
+
"Explores solution space via quantum superposition (simulated)",
|
|
11523
|
+
"Useful for benchmarking against real quantum results",
|
|
11524
|
+
"Runs entirely in-browser or on backend \u2014 no API keys needed"
|
|
11525
|
+
],
|
|
11526
|
+
weaknesses: [
|
|
11527
|
+
"Exponential classical overhead \u2014 limited to ~20 qubits",
|
|
11528
|
+
"Cannot capture true quantum speedup",
|
|
11529
|
+
"QUBO encoding may not be optimal for all problem structures",
|
|
11530
|
+
"Variational optimization can get stuck in local minima"
|
|
11531
|
+
],
|
|
11532
|
+
bestFor: "Small problems where you want to preview quantum approaches before investing in real hardware",
|
|
11533
|
+
wikipedia: "https://en.wikipedia.org/wiki/Quantum_approximate_optimization_algorithm",
|
|
11534
|
+
otherResources: [
|
|
11535
|
+
{ label: "Farhi et al. (2014) \u2014 Original QAOA Paper", url: "https://arxiv.org/abs/1411.4028" },
|
|
11536
|
+
{ label: "QUBO Formulation Guide", url: "https://en.wikipedia.org/wiki/Quadratic_unconstrained_binary_optimization" }
|
|
11537
|
+
]
|
|
11538
|
+
},
|
|
11539
|
+
"ionq-qaoa": {
|
|
11540
|
+
icon: "\u269B\uFE0F",
|
|
11541
|
+
// ⚛️
|
|
11542
|
+
color: "danger",
|
|
11543
|
+
complexity: "Minutes (queue + execution)",
|
|
11544
|
+
requires: "IonQ API key + credits",
|
|
11545
|
+
available: false,
|
|
11546
|
+
tagline: "Real quantum hardware optimization",
|
|
11547
|
+
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.",
|
|
11548
|
+
howItWorks: [
|
|
11549
|
+
"Encode scheduling problem as QUBO matrix",
|
|
11550
|
+
"Compile QAOA circuit to IonQ\u2019s native gate set (MS, GPi, GPi2)",
|
|
11551
|
+
"Submit job to IonQ cloud via API",
|
|
11552
|
+
"Queue for hardware execution (trapped-ion processor)",
|
|
11553
|
+
"Execute quantum circuit with real qubits",
|
|
11554
|
+
"Retrieve measurement results and decode to schedule"
|
|
11555
|
+
],
|
|
11556
|
+
keyParameters: [
|
|
11557
|
+
{ name: "QAOA Depth (p)", value: "Number of alternating layers (default: 1\u20132 for NISQ)" },
|
|
11558
|
+
{ name: "Backend", value: "IonQ Harmony (11 qubits) or Aria (25 qubits)" },
|
|
11559
|
+
{ name: "Shots", value: "Number of circuit executions (1024)" },
|
|
11560
|
+
{ name: "Error Mitigation", value: "IonQ debiasing enabled by default" }
|
|
11561
|
+
],
|
|
11562
|
+
strengths: [
|
|
11563
|
+
"Real quantum computation with genuine quantum effects",
|
|
11564
|
+
"Trapped-ion qubits have high gate fidelity (~99.5%)",
|
|
11565
|
+
"All-to-all connectivity \u2014 no SWAP overhead",
|
|
11566
|
+
"Potential for quantum advantage on certain problem classes"
|
|
11567
|
+
],
|
|
11568
|
+
weaknesses: [
|
|
11569
|
+
"Requires IonQ API key and credits (not free)",
|
|
11570
|
+
"Queue wait times can be minutes to hours",
|
|
11571
|
+
"Limited to ~25 qubits (current hardware)",
|
|
11572
|
+
"NISQ noise limits circuit depth and solution quality"
|
|
11573
|
+
],
|
|
11574
|
+
bestFor: "Exploring real quantum optimization on small problem instances when you have IonQ access",
|
|
11575
|
+
wikipedia: "https://en.wikipedia.org/wiki/Trapped-ion_quantum_computer",
|
|
11576
|
+
otherResources: [
|
|
11577
|
+
{ label: "IonQ Documentation", url: "https://docs.ionq.com/" },
|
|
11578
|
+
{ label: "IonQ Aria Specs", url: "https://ionq.com/quantum-systems/aria" },
|
|
11579
|
+
{ label: "QAOA on NISQ Devices", url: "https://arxiv.org/abs/1812.01041" }
|
|
11580
|
+
]
|
|
11581
|
+
}
|
|
11582
|
+
};
|
|
11583
|
+
|
|
11584
|
+
// ../../b4m-core/packages/quantum/dist/src/solvers/index.js
|
|
11585
|
+
var allSolvers = [
|
|
11586
|
+
greedySolver,
|
|
11587
|
+
naiveSolver,
|
|
11588
|
+
randomRestartSolver,
|
|
11589
|
+
simulatedAnnealingSolver,
|
|
11590
|
+
simulatedAnnealingMediumSolver,
|
|
11591
|
+
simulatedAnnealingLargeSolver,
|
|
11592
|
+
tabuSolver,
|
|
11593
|
+
geneticAlgorithmSolver,
|
|
11594
|
+
antColonySolver
|
|
11595
|
+
];
|
|
11596
|
+
var solverRegistry = new Map(allSolvers.map((s) => [s.id, s]));
|
|
11597
|
+
function getSolver(id) {
|
|
11598
|
+
return solverRegistry.get(id);
|
|
11599
|
+
}
|
|
11600
|
+
function getAvailableSolverIds() {
|
|
11601
|
+
return Array.from(solverRegistry.keys());
|
|
11602
|
+
}
|
|
11603
|
+
var displaySolvers = [
|
|
11604
|
+
...allSolvers.map((s) => ({ id: s.id, name: s.name, description: s.description })),
|
|
11605
|
+
{ id: "highs", name: "HiGHS (WASM)", description: solverMetadata.highs.tagline },
|
|
11606
|
+
{ id: "simulated-qaoa", name: "Simulated QAOA", description: solverMetadata["simulated-qaoa"].tagline },
|
|
11607
|
+
{ id: "ionq-qaoa", name: "IonQ QAOA", description: solverMetadata["ionq-qaoa"].tagline }
|
|
11608
|
+
];
|
|
11609
|
+
|
|
11610
|
+
// ../../b4m-core/packages/quantum/dist/src/prompts/system-prompt.js
|
|
11611
|
+
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.
|
|
11612
|
+
|
|
11613
|
+
## Your Capabilities
|
|
11614
|
+
|
|
11615
|
+
You have two specialized tools:
|
|
11616
|
+
|
|
11617
|
+
### quantum_formulate
|
|
11618
|
+
Converts natural language descriptions into structured SchedulingProblem definitions. When a user describes a scheduling scenario in plain English, use this tool to extract:
|
|
11619
|
+
- Jobs (ordered sequences of operations)
|
|
11620
|
+
- Machines (resources that process operations)
|
|
11621
|
+
- Operation durations and machine assignments
|
|
11622
|
+
- Precedence constraints (operations within a job must execute in order)
|
|
11623
|
+
|
|
11624
|
+
### quantum_schedule
|
|
11625
|
+
Runs optimization solvers on a structured SchedulingProblem. Available solvers:
|
|
11626
|
+
${allSolvers.map((s) => `- **${s.name}** (\`${s.id}\`): ${s.description}`).join("\n")}
|
|
11627
|
+
|
|
11628
|
+
## How to Help Users
|
|
11629
|
+
|
|
11630
|
+
### Problem Formulation
|
|
11631
|
+
When a user describes a scheduling scenario:
|
|
11632
|
+
1. Use \`quantum_formulate\` to convert their description into a structured problem
|
|
11633
|
+
2. Present the formulated problem clearly, showing jobs, machines, and operation sequences
|
|
11634
|
+
3. Ask if adjustments are needed before solving
|
|
11635
|
+
|
|
11636
|
+
### Running Solvers
|
|
11637
|
+
When solving a problem:
|
|
11638
|
+
1. Start with the \`greedy\` solver for a quick baseline result
|
|
11639
|
+
2. For better solutions, suggest running multiple solvers: \`greedy\`, \`simulated-annealing\`, \`tabu\`, \`genetic-algorithm\`
|
|
11640
|
+
3. Explain that metaheuristic solvers explore larger solution spaces but take longer
|
|
11641
|
+
4. Compare results across solvers when multiple are run
|
|
11642
|
+
|
|
11643
|
+
### Interpreting Results
|
|
11644
|
+
When presenting solver results:
|
|
11645
|
+
- Explain the **makespan** (total schedule length) and why lower is better
|
|
11646
|
+
- Walk through the schedule showing which jobs run on which machines at what times
|
|
11647
|
+
- Identify bottleneck machines (those with the most continuous utilization)
|
|
11648
|
+
- Suggest improvements if the problem structure allows (e.g., rebalancing operations)
|
|
11649
|
+
|
|
11650
|
+
### QUBO Encoding
|
|
11651
|
+
If asked about quantum computing aspects:
|
|
11652
|
+
- Explain how scheduling problems are encoded as QUBO (Quadratic Unconstrained Binary Optimization) matrices
|
|
11653
|
+
- Time-indexed binary variables: x_{o,t} = 1 if operation o starts at time t
|
|
11654
|
+
- Three constraint families: exactly-once (each operation starts exactly once), no-overlap (no two operations on same machine at same time), precedence (operations within a job maintain order)
|
|
11655
|
+
- QUBO matrices can be solved by quantum annealers (D-Wave) or quantum-inspired classical solvers
|
|
11656
|
+
|
|
11657
|
+
## Communication Style
|
|
11658
|
+
- Be precise with numbers and scheduling terminology
|
|
11659
|
+
- Use clear formatting: tables for schedules, bullet points for comparisons
|
|
11660
|
+
- When explaining optimization concepts, ground them in the user's specific problem
|
|
11661
|
+
- Proactively suggest next steps (e.g., "Would you like to try more solvers?" or "Should I adjust the problem?")
|
|
11662
|
+
|
|
11663
|
+
## Important Notes
|
|
11664
|
+
- All solvers run client-side in the browser via Web Workers \u2014 no server compute needed
|
|
11665
|
+
- The greedy solver is deterministic and instant; metaheuristic solvers involve randomness
|
|
11666
|
+
- For very large problems (50+ operations), recommend the greedy and simulated-annealing solvers
|
|
11667
|
+
- The HiGHS MIP solver uses WASM and provides optimal solutions for small problems but may be slow on large ones`;
|
|
11668
|
+
var PROBLEM_FORMULATION_PROMPT = `You are a job-shop scheduling problem formulator. Convert the user's natural language description into a structured SchedulingProblem JSON object.
|
|
11669
|
+
|
|
11670
|
+
RULES:
|
|
11671
|
+
1. Each job has an id (number starting from 0), a name, and an ordered array of operations
|
|
11672
|
+
2. Each machine has an id (number starting from 0) and a name
|
|
11673
|
+
3. Each operation has jobId (matching its parent job), machineId (which machine it runs on), and duration (positive integer)
|
|
11674
|
+
4. Operations within a job execute in order (first operation must finish before second starts)
|
|
11675
|
+
5. Each machine can only process one operation at a time
|
|
11676
|
+
6. Assign reasonable durations if not specified (use whole numbers)
|
|
11677
|
+
7. Create meaningful names for jobs and machines based on context
|
|
11678
|
+
|
|
11679
|
+
RESPOND WITH ONLY A JSON OBJECT matching this schema:
|
|
11680
|
+
{
|
|
11681
|
+
"name": "string - descriptive problem name",
|
|
11682
|
+
"description": "string - brief description",
|
|
11683
|
+
"jobs": [
|
|
11684
|
+
{
|
|
11685
|
+
"id": 0,
|
|
11686
|
+
"name": "Job Name",
|
|
11687
|
+
"operations": [
|
|
11688
|
+
{ "jobId": 0, "machineId": 0, "duration": 3 }
|
|
11689
|
+
]
|
|
11690
|
+
}
|
|
11691
|
+
],
|
|
11692
|
+
"machines": [
|
|
11693
|
+
{ "id": 0, "name": "Machine Name" }
|
|
11694
|
+
]
|
|
11695
|
+
}
|
|
11696
|
+
|
|
11697
|
+
No markdown, no explanation, no code blocks \u2014 just the raw JSON object.`;
|
|
11698
|
+
|
|
11699
|
+
// ../../b4m-core/packages/services/dist/src/llm/tools/implementation/quantumSchedule/index.js
|
|
11700
|
+
function formatResult(result) {
|
|
11701
|
+
const lines = [
|
|
11702
|
+
`Solver: ${result.solverName} (${result.solverId})`,
|
|
11703
|
+
`Makespan: ${result.makespan}`,
|
|
11704
|
+
`Elapsed: ${result.elapsedMs.toFixed(1)}ms`
|
|
11705
|
+
];
|
|
11706
|
+
if (result.iterations !== void 0) {
|
|
11707
|
+
lines.push(`Iterations: ${result.iterations}`);
|
|
11708
|
+
}
|
|
11709
|
+
lines.push("Schedule:");
|
|
11710
|
+
const byMachine = /* @__PURE__ */ new Map();
|
|
11711
|
+
for (const op of result.schedule) {
|
|
11712
|
+
if (!byMachine.has(op.machineId))
|
|
11713
|
+
byMachine.set(op.machineId, []);
|
|
11714
|
+
byMachine.get(op.machineId).push(op);
|
|
11715
|
+
}
|
|
11716
|
+
for (const [machineId, ops] of byMachine) {
|
|
11717
|
+
const sorted = ops.sort((a, b) => a.startTime - b.startTime);
|
|
11718
|
+
const timeline = sorted.map((op) => `Job${op.jobId}[${op.startTime}-${op.endTime}]`).join(" -> ");
|
|
11719
|
+
lines.push(` Machine ${machineId}: ${timeline}`);
|
|
11720
|
+
}
|
|
11721
|
+
return lines.join("\n");
|
|
11722
|
+
}
|
|
11723
|
+
async function runQuantumSchedule(params) {
|
|
11724
|
+
const parseResult = SchedulingProblemSchema.safeParse(params.problem);
|
|
11725
|
+
if (!parseResult.success) {
|
|
11726
|
+
return `Error: Invalid scheduling problem format.
|
|
11727
|
+
${parseResult.error.issues.map((i) => ` - ${i.path.join(".")}: ${i.message}`).join("\n")}
|
|
11728
|
+
|
|
11729
|
+
Expected format: { name, jobs: [{ id, name, operations: [{ jobId, machineId, duration }] }], machines: [{ id, name }] }`;
|
|
11730
|
+
}
|
|
11731
|
+
const problem = parseResult.data;
|
|
11732
|
+
let solverIds;
|
|
11733
|
+
if (params.solverIds && params.solverIds.length > 0) {
|
|
11734
|
+
const invalid = params.solverIds.filter((id) => !SolverIdSchema.safeParse(id).success);
|
|
11735
|
+
if (invalid.length > 0) {
|
|
11736
|
+
return `Error: Unknown solver IDs: ${invalid.join(", ")}.
|
|
11737
|
+
Available solvers: ${getAvailableSolverIds().join(", ")}`;
|
|
11738
|
+
}
|
|
11739
|
+
solverIds = params.solverIds;
|
|
11740
|
+
} else {
|
|
11741
|
+
solverIds = ["greedy"];
|
|
11742
|
+
}
|
|
11743
|
+
const timeoutMs = params.timeoutMs ?? 1e4;
|
|
11744
|
+
const results = [];
|
|
11745
|
+
const errors = [];
|
|
11746
|
+
for (const solverId of solverIds) {
|
|
11747
|
+
const solver = getSolver(solverId);
|
|
11748
|
+
if (!solver) {
|
|
11749
|
+
errors.push(`Solver "${solverId}" not found`);
|
|
11750
|
+
continue;
|
|
11751
|
+
}
|
|
11752
|
+
try {
|
|
11753
|
+
const result = await solver.solve(problem, { timeoutMs });
|
|
11754
|
+
results.push(result);
|
|
11755
|
+
} catch (err) {
|
|
11756
|
+
errors.push(`${solver.name}: ${err instanceof Error ? err.message : String(err)}`);
|
|
11757
|
+
}
|
|
11758
|
+
}
|
|
11759
|
+
if (results.length === 0) {
|
|
11760
|
+
return `Error: No solvers completed successfully.
|
|
11761
|
+
${errors.join("\n")}`;
|
|
11762
|
+
}
|
|
11763
|
+
results.sort((a, b) => a.makespan - b.makespan);
|
|
11764
|
+
const lines = [];
|
|
11765
|
+
lines.push(`## Scheduling Results for "${problem.name}"`);
|
|
11766
|
+
lines.push(`Problem: ${problem.jobs.length} jobs, ${problem.machines.length} machines, ${problem.jobs.reduce((s, j) => s + j.operations.length, 0)} operations`);
|
|
11767
|
+
lines.push("");
|
|
11768
|
+
if (results.length > 1) {
|
|
11769
|
+
lines.push(`### Winner: ${results[0].solverName} (makespan: ${results[0].makespan})`);
|
|
11770
|
+
lines.push("");
|
|
11771
|
+
}
|
|
11772
|
+
for (const result of results) {
|
|
11773
|
+
lines.push(`### ${result.solverName}`);
|
|
11774
|
+
lines.push(formatResult(result));
|
|
11775
|
+
lines.push("");
|
|
11776
|
+
}
|
|
11777
|
+
if (errors.length > 0) {
|
|
11778
|
+
lines.push("### Errors");
|
|
11779
|
+
errors.forEach((e) => lines.push(`- ${e}`));
|
|
11780
|
+
}
|
|
11781
|
+
return lines.join("\n");
|
|
11782
|
+
}
|
|
11783
|
+
var quantumScheduleTool = {
|
|
11784
|
+
name: "quantum_schedule",
|
|
11785
|
+
implementation: () => ({
|
|
11786
|
+
toolFn: (value) => runQuantumSchedule(value),
|
|
11787
|
+
toolSchema: {
|
|
11788
|
+
name: "quantum_schedule",
|
|
11789
|
+
description: `Run quantum-inspired optimization solvers on a job-shop scheduling problem. Finds optimal or near-optimal schedules that minimize makespan (total completion time).
|
|
11790
|
+
|
|
11791
|
+
Available solvers: ${allSolvers.map((s) => `${s.id} (${s.name})`).join(", ")}.
|
|
11792
|
+
|
|
11793
|
+
The "greedy" solver runs instantly. Metaheuristic solvers (simulated-annealing, tabu, genetic-algorithm, ant-colony) explore larger solution spaces but take longer. Use multiple solvers to race them against each other.
|
|
11794
|
+
|
|
11795
|
+
The problem must define jobs (each with ordered operations) and machines. Each operation runs on a specific machine for a specific duration. Operations within a job must run in order.`,
|
|
11796
|
+
parameters: {
|
|
11797
|
+
type: "object",
|
|
11798
|
+
properties: {
|
|
11799
|
+
problem: {
|
|
11800
|
+
type: "object",
|
|
11801
|
+
description: "The scheduling problem to solve. Must include name, jobs array, and machines array.",
|
|
11802
|
+
properties: {
|
|
11803
|
+
name: { type: "string", description: "Name of the scheduling problem" },
|
|
11804
|
+
description: { type: "string", description: "Optional description" },
|
|
11805
|
+
jobs: {
|
|
11806
|
+
type: "array",
|
|
11807
|
+
description: "Array of jobs, each with id, name, and operations",
|
|
11808
|
+
items: {
|
|
11809
|
+
type: "object",
|
|
11810
|
+
properties: {
|
|
11811
|
+
id: { type: "number" },
|
|
11812
|
+
name: { type: "string" },
|
|
11813
|
+
operations: {
|
|
11814
|
+
type: "array",
|
|
11815
|
+
items: {
|
|
11816
|
+
type: "object",
|
|
11817
|
+
properties: {
|
|
11818
|
+
jobId: { type: "number", description: "Must match the parent job id" },
|
|
11819
|
+
machineId: { type: "number", description: "Which machine this operation runs on" },
|
|
11820
|
+
duration: { type: "number", description: "Processing time" }
|
|
11821
|
+
},
|
|
11822
|
+
required: ["jobId", "machineId", "duration"]
|
|
11823
|
+
}
|
|
11824
|
+
}
|
|
11825
|
+
},
|
|
11826
|
+
required: ["id", "name", "operations"]
|
|
11827
|
+
}
|
|
11828
|
+
},
|
|
11829
|
+
machines: {
|
|
11830
|
+
type: "array",
|
|
11831
|
+
description: "Array of machines with id and name",
|
|
11832
|
+
items: {
|
|
11833
|
+
type: "object",
|
|
11834
|
+
properties: {
|
|
11835
|
+
id: { type: "number" },
|
|
11836
|
+
name: { type: "string" }
|
|
11837
|
+
},
|
|
11838
|
+
required: ["id", "name"]
|
|
11839
|
+
}
|
|
11840
|
+
}
|
|
11841
|
+
},
|
|
11842
|
+
required: ["name", "jobs", "machines"]
|
|
11843
|
+
},
|
|
11844
|
+
solverIds: {
|
|
11845
|
+
type: "array",
|
|
11846
|
+
items: { type: "string" },
|
|
11847
|
+
description: `Which solvers to run. Defaults to ["greedy"]. Available: ${getAvailableSolverIds().join(", ")}. Use multiple to race solvers.`
|
|
11848
|
+
},
|
|
11849
|
+
timeoutMs: {
|
|
11850
|
+
type: "number",
|
|
11851
|
+
description: "Maximum time per solver in milliseconds (default: 10000)"
|
|
11852
|
+
}
|
|
11853
|
+
},
|
|
11854
|
+
required: ["problem"]
|
|
11855
|
+
}
|
|
11856
|
+
}
|
|
11857
|
+
})
|
|
11858
|
+
};
|
|
11859
|
+
|
|
11860
|
+
// ../../b4m-core/packages/services/dist/src/llm/tools/implementation/quantumFormulate/index.js
|
|
11861
|
+
var quantumFormulateTool = {
|
|
11862
|
+
name: "quantum_formulate",
|
|
11863
|
+
implementation: (context) => ({
|
|
11864
|
+
toolFn: async (value) => {
|
|
11865
|
+
const params = value;
|
|
11866
|
+
const userDescription = params.description?.trim();
|
|
11867
|
+
if (!userDescription) {
|
|
11868
|
+
return "Error: Please provide a description of the scheduling problem to formulate.";
|
|
11869
|
+
}
|
|
11870
|
+
try {
|
|
11871
|
+
let rawContent = "";
|
|
11872
|
+
await context.llm.complete(context.model ?? "gpt-4", [
|
|
11873
|
+
{ role: "system", content: PROBLEM_FORMULATION_PROMPT },
|
|
11874
|
+
{ role: "user", content: userDescription }
|
|
11875
|
+
], { temperature: 0.2, stream: false }, async (texts) => {
|
|
11876
|
+
rawContent = texts.filter((t) => t !== null && t !== void 0).join("");
|
|
11877
|
+
});
|
|
11878
|
+
let jsonStr = rawContent.trim();
|
|
11879
|
+
const codeBlockMatch = jsonStr.match(/```(?:json)?\s*([\s\S]*?)```/);
|
|
11880
|
+
if (codeBlockMatch) {
|
|
11881
|
+
jsonStr = codeBlockMatch[1].trim();
|
|
11882
|
+
}
|
|
11883
|
+
let parsed;
|
|
11884
|
+
try {
|
|
11885
|
+
parsed = JSON.parse(jsonStr);
|
|
11886
|
+
} catch {
|
|
11887
|
+
return `Error: Could not parse LLM response as JSON. Raw response:
|
|
11888
|
+
${rawContent}
|
|
11889
|
+
|
|
11890
|
+
Please try rephrasing your description with more specific details about jobs, machines, and processing times.`;
|
|
11891
|
+
}
|
|
11892
|
+
const validated = SchedulingProblemSchema.safeParse(parsed);
|
|
11893
|
+
if (!validated.success) {
|
|
11894
|
+
return `Error: LLM generated an invalid problem structure.
|
|
11895
|
+
${validated.error.issues.map((i) => ` - ${i.path.join(".")}: ${i.message}`).join("\n")}
|
|
11896
|
+
|
|
11897
|
+
Raw output:
|
|
11898
|
+
${jsonStr}
|
|
11899
|
+
|
|
11900
|
+
Please try rephrasing your description.`;
|
|
11901
|
+
}
|
|
11902
|
+
const problem = validated.data;
|
|
11903
|
+
const totalOps = problem.jobs.reduce((s, j) => s + j.operations.length, 0);
|
|
11904
|
+
const lines = [
|
|
11905
|
+
`## Formulated: "${problem.name}"`,
|
|
11906
|
+
problem.description ? `
|
|
11907
|
+
${problem.description}` : "",
|
|
11908
|
+
"",
|
|
11909
|
+
`**${problem.jobs.length} jobs, ${problem.machines.length} machines, ${totalOps} operations**`,
|
|
11910
|
+
"",
|
|
11911
|
+
"### Jobs:",
|
|
11912
|
+
...problem.jobs.map((j) => {
|
|
11913
|
+
const ops = j.operations.map((op, i) => {
|
|
11914
|
+
const machine = problem.machines.find((m) => m.id === op.machineId);
|
|
11915
|
+
return `Step ${i + 1}: ${machine?.name ?? `Machine ${op.machineId}`} (${op.duration}t)`;
|
|
11916
|
+
});
|
|
11917
|
+
return `- **${j.name}**: ${ops.join(" -> ")}`;
|
|
11918
|
+
}),
|
|
11919
|
+
"",
|
|
11920
|
+
"### Machines:",
|
|
11921
|
+
...problem.machines.map((m) => `- ${m.name} (id: ${m.id})`),
|
|
11922
|
+
"",
|
|
11923
|
+
"### Structured Problem (JSON):",
|
|
11924
|
+
"```json",
|
|
11925
|
+
JSON.stringify(problem, null, 2),
|
|
11926
|
+
"```",
|
|
11927
|
+
"",
|
|
11928
|
+
"You can now use the `quantum_schedule` tool to solve this problem with various optimization algorithms."
|
|
11929
|
+
];
|
|
11930
|
+
return lines.filter((l) => l !== void 0).join("\n");
|
|
11931
|
+
} catch (err) {
|
|
11932
|
+
return `Error formulating problem: ${err instanceof Error ? err.message : String(err)}`;
|
|
11933
|
+
}
|
|
11934
|
+
},
|
|
11935
|
+
toolSchema: {
|
|
11936
|
+
name: "quantum_formulate",
|
|
11937
|
+
description: `Convert a natural language description of a scheduling/optimization problem into a structured SchedulingProblem that can be solved by the quantum_schedule tool.
|
|
11938
|
+
|
|
11939
|
+
Describe the problem in plain English \u2014 what jobs need to be done, what machines or resources are available, how long each step takes, and any ordering constraints. The tool will produce a structured problem definition.
|
|
11940
|
+
|
|
11941
|
+
Examples:
|
|
11942
|
+
- "I have 3 orders to manufacture. Each needs cutting, welding, and painting. Cutting takes 2h, welding 3h, painting 1h."
|
|
11943
|
+
- "Schedule 4 software builds across 2 CI runners. Build A needs compile (5min) then test (3min)..."
|
|
11944
|
+
- "A bakery needs to make 5 cakes. Each cake goes through mixing, baking, and decorating on separate stations."`,
|
|
11945
|
+
parameters: {
|
|
11946
|
+
type: "object",
|
|
11947
|
+
properties: {
|
|
11948
|
+
description: {
|
|
11949
|
+
type: "string",
|
|
11950
|
+
description: "Natural language description of the scheduling problem to formulate"
|
|
11951
|
+
}
|
|
11952
|
+
},
|
|
11953
|
+
required: ["description"]
|
|
11954
|
+
}
|
|
11955
|
+
}
|
|
11956
|
+
})
|
|
11957
|
+
};
|
|
11958
|
+
|
|
11959
|
+
// ../../b4m-core/packages/services/dist/src/llm/tools/implementation/navigateView/index.js
|
|
11960
|
+
var navigateViewTool = {
|
|
11961
|
+
name: "navigate_view",
|
|
11962
|
+
implementation: (context) => ({
|
|
11963
|
+
toolFn: async (value) => {
|
|
11964
|
+
const params = value;
|
|
11965
|
+
const { suggestions } = params;
|
|
11966
|
+
context.logger.log("\u{1F9ED} navigate_view: Received suggestions:", JSON.stringify(suggestions));
|
|
11967
|
+
if (!suggestions || !Array.isArray(suggestions) || suggestions.length === 0) {
|
|
11968
|
+
return "No navigation suggestions provided.";
|
|
11969
|
+
}
|
|
11970
|
+
const capped = suggestions.slice(0, 3);
|
|
11971
|
+
const invalid = capped.filter((s) => !getViewById(s.viewId));
|
|
11972
|
+
if (invalid.length > 0) {
|
|
11973
|
+
context.logger.log("\u{1F9ED} navigate_view: Unknown viewIds:", invalid.map((s) => s.viewId));
|
|
11974
|
+
}
|
|
11975
|
+
const isAdmin = context.user?.isAdmin === true;
|
|
11976
|
+
const intents = resolveNavigationIntents(capped, isAdmin);
|
|
11977
|
+
if (intents.length === 0) {
|
|
11978
|
+
return "No valid navigation views matched the provided viewIds.";
|
|
11979
|
+
}
|
|
11980
|
+
context.logger.log("\u{1F9ED} navigate_view: Resolved intents:", intents.map((i) => i.viewId));
|
|
11981
|
+
return JSON.stringify({
|
|
11982
|
+
__navigationIntents: true,
|
|
11983
|
+
intents,
|
|
11984
|
+
message: `Suggested ${intents.length} navigation option(s): ${intents.map((i) => i.label).join(", ")}`
|
|
11985
|
+
});
|
|
11986
|
+
},
|
|
11987
|
+
toolSchema: {
|
|
11988
|
+
name: "navigate_view",
|
|
11989
|
+
description: "Suggest navigation to relevant app views. Returns inline action buttons the user can click. ALWAYS use this tool when your response discusses a topic that has a matching view (e.g., scheduling \u2192 opti.scheduling, user management \u2192 admin.users). Call this tool alongside your text answer \u2014 answer the question AND suggest where to go.",
|
|
11990
|
+
parameters: {
|
|
11991
|
+
type: "object",
|
|
11992
|
+
properties: {
|
|
11993
|
+
suggestions: {
|
|
11994
|
+
type: "array",
|
|
11995
|
+
description: "Navigation suggestions (1-3 items). Each suggests a view the user might want to visit.",
|
|
11996
|
+
items: {
|
|
11997
|
+
type: "object",
|
|
11998
|
+
properties: {
|
|
11999
|
+
viewId: {
|
|
12000
|
+
type: "string",
|
|
12001
|
+
description: 'The view ID from the available views list (e.g., "opti.scheduling", "admin.users")'
|
|
12002
|
+
},
|
|
12003
|
+
reason: {
|
|
12004
|
+
type: "string",
|
|
12005
|
+
description: "Brief reason why this view is relevant (max 80 chars, shown as tooltip)"
|
|
12006
|
+
}
|
|
12007
|
+
},
|
|
12008
|
+
required: ["viewId", "reason"]
|
|
12009
|
+
},
|
|
12010
|
+
maxItems: 3,
|
|
12011
|
+
minItems: 1
|
|
12012
|
+
}
|
|
12013
|
+
},
|
|
12014
|
+
required: ["suggestions"]
|
|
12015
|
+
}
|
|
12016
|
+
}
|
|
12017
|
+
})
|
|
12018
|
+
};
|
|
12019
|
+
|
|
12020
|
+
// ../../b4m-core/packages/services/dist/src/llm/tools/implementation/bashExecute/index.js
|
|
12021
|
+
import { spawn } from "child_process";
|
|
12022
|
+
import path13 from "path";
|
|
12023
|
+
var DEFAULT_TIMEOUT_MS = 6e4;
|
|
12024
|
+
var MAX_OUTPUT_SIZE = 100 * 1024;
|
|
12025
|
+
var DANGEROUS_PATTERNS = [
|
|
12026
|
+
// Destructive file operations
|
|
12027
|
+
{
|
|
12028
|
+
pattern: /\brm\s+(-[a-zA-Z]*r[a-zA-Z]*\s+|.*\s+-[a-zA-Z]*r).*\//i,
|
|
12029
|
+
reason: "Recursive delete with path",
|
|
12030
|
+
block: true
|
|
12031
|
+
},
|
|
12032
|
+
{
|
|
12033
|
+
pattern: /\brm\s+(-[a-zA-Z]*f[a-zA-Z]*\s+|.*\s+-[a-zA-Z]*f).*--no-preserve-root/i,
|
|
12034
|
+
reason: "Force delete without preserve root",
|
|
12035
|
+
block: true
|
|
12036
|
+
},
|
|
12037
|
+
{
|
|
12038
|
+
pattern: /\brm\s+-[a-zA-Z]*r[a-zA-Z]*f[a-zA-Z]*\s+\//i,
|
|
12039
|
+
reason: "Recursive force delete on root paths",
|
|
12040
|
+
block: true
|
|
12041
|
+
},
|
|
12042
|
+
{
|
|
12043
|
+
pattern: /\brm\s+-[a-zA-Z]*f[a-zA-Z]*r[a-zA-Z]*\s+\//i,
|
|
12044
|
+
reason: "Force recursive delete on root paths",
|
|
12045
|
+
block: true
|
|
12046
|
+
},
|
|
12047
|
+
// System-level dangerous commands
|
|
12048
|
+
{ pattern: /\bsudo\b/i, reason: "Elevated privileges (sudo)", block: true },
|
|
12049
|
+
{ pattern: /\bsu\s+(-|root)/i, reason: "Switch to root user", block: true },
|
|
12050
|
+
{ pattern: /\bchmod\s+777\b/i, reason: "Overly permissive chmod", block: false },
|
|
12051
|
+
{ pattern: /\bchown\s+-R\s+root/i, reason: "Recursive chown to root", block: true },
|
|
12052
|
+
// Disk/partition operations
|
|
12053
|
+
{ pattern: /\bmkfs\b/i, reason: "Filesystem creation", block: true },
|
|
12054
|
+
{ pattern: /\bfdisk\b/i, reason: "Disk partitioning", block: true },
|
|
12055
|
+
{ pattern: /\bdd\s+.*of=\/dev\//i, reason: "Direct disk write", block: true },
|
|
12056
|
+
// Network attacks
|
|
12057
|
+
{ pattern: /\b(nc|netcat)\s+.*-e\s+\/bin\/(ba)?sh/i, reason: "Reverse shell attempt", block: true },
|
|
12058
|
+
{ pattern: /\bcurl\s+.*\|\s*(ba)?sh/i, reason: "Piping remote script to shell", block: true },
|
|
12059
|
+
{ pattern: /\bwget\s+.*\|\s*(ba)?sh/i, reason: "Piping remote script to shell", block: true },
|
|
12060
|
+
// Fork bombs and resource exhaustion
|
|
12061
|
+
{ pattern: /:\(\)\s*\{\s*:\|:&\s*\}\s*;/i, reason: "Fork bomb", block: true },
|
|
12062
|
+
{ pattern: /\bwhile\s+true.*do.*done.*&/i, reason: "Infinite loop in background", block: false },
|
|
12063
|
+
// Credential/sensitive data access
|
|
12064
|
+
{ pattern: /\/etc\/shadow/i, reason: "Access to shadow file", block: true },
|
|
12065
|
+
{ pattern: /\/etc\/passwd.*>/i, reason: "Modifying passwd file", block: true },
|
|
12066
|
+
{ pattern: /\baws\s+.*--profile\s+/i, reason: "AWS profile access", block: false },
|
|
12067
|
+
// History/log manipulation
|
|
12068
|
+
{ pattern: /\bhistory\s+-c\b/i, reason: "Clearing shell history", block: false },
|
|
12069
|
+
{ pattern: />\s*\/var\/log\//i, reason: "Overwriting system logs", block: true },
|
|
12070
|
+
// Dangerous redirects
|
|
12071
|
+
{ pattern: />\s*\/dev\/sda/i, reason: "Writing to block device", block: true },
|
|
12072
|
+
{ pattern: />\s*\/dev\/null.*2>&1.*</i, reason: "Potentially hiding output", block: false },
|
|
12073
|
+
// Environment manipulation
|
|
12074
|
+
{ pattern: /\bexport\s+PATH\s*=\s*[^$]/i, reason: "Overwriting PATH", block: false },
|
|
12075
|
+
{ pattern: /\bexport\s+LD_PRELOAD/i, reason: "LD_PRELOAD manipulation", block: true },
|
|
12076
|
+
// Process/system control
|
|
12077
|
+
{ pattern: /\bkill\s+-9\s+(-1|1)\b/i, reason: "Killing all processes", block: true },
|
|
12078
|
+
{ pattern: /\bkillall\s+-9\b/i, reason: "Force killing processes", block: false },
|
|
12079
|
+
{ pattern: /\bshutdown\b/i, reason: "System shutdown", block: true },
|
|
12080
|
+
{ pattern: /\breboot\b/i, reason: "System reboot", block: true },
|
|
12081
|
+
{ pattern: /\binit\s+[06]\b/i, reason: "System runlevel change", block: true }
|
|
12082
|
+
];
|
|
12083
|
+
var SAFE_COMMAND_PREFIXES = [
|
|
12084
|
+
"ls",
|
|
12085
|
+
"cat",
|
|
12086
|
+
"head",
|
|
12087
|
+
"tail",
|
|
12088
|
+
"grep",
|
|
12089
|
+
"find",
|
|
12090
|
+
"echo",
|
|
12091
|
+
"pwd",
|
|
12092
|
+
"whoami",
|
|
12093
|
+
"date",
|
|
12094
|
+
"cal",
|
|
12095
|
+
"wc",
|
|
12096
|
+
"sort",
|
|
12097
|
+
"uniq",
|
|
12098
|
+
"cut",
|
|
10102
12099
|
"tr",
|
|
10103
12100
|
"sed",
|
|
10104
12101
|
"awk",
|
|
@@ -10206,7 +12203,7 @@ async function executeBashCommand(params) {
|
|
|
10206
12203
|
};
|
|
10207
12204
|
}
|
|
10208
12205
|
const baseCwd = process.cwd();
|
|
10209
|
-
const targetCwd = relativeCwd ?
|
|
12206
|
+
const targetCwd = relativeCwd ? path13.resolve(baseCwd, relativeCwd) : baseCwd;
|
|
10210
12207
|
const effectiveTimeout = Math.min(timeout, 5 * 60 * 1e3);
|
|
10211
12208
|
return new Promise((resolve3) => {
|
|
10212
12209
|
let stdout = "";
|
|
@@ -10269,7 +12266,7 @@ async function executeBashCommand(params) {
|
|
|
10269
12266
|
});
|
|
10270
12267
|
});
|
|
10271
12268
|
}
|
|
10272
|
-
function
|
|
12269
|
+
function formatResult2(result, command) {
|
|
10273
12270
|
const parts = [];
|
|
10274
12271
|
parts.push(`$ ${command}`);
|
|
10275
12272
|
parts.push("");
|
|
@@ -10323,7 +12320,7 @@ var bashExecuteTool = {
|
|
|
10323
12320
|
}
|
|
10324
12321
|
try {
|
|
10325
12322
|
const result = await executeBashCommand(params);
|
|
10326
|
-
const formattedResult =
|
|
12323
|
+
const formattedResult = formatResult2(result, params.command);
|
|
10327
12324
|
context.logger.info("Bash: Command completed", {
|
|
10328
12325
|
exitCode: result.exitCode,
|
|
10329
12326
|
timedOut: result.timedOut,
|
|
@@ -11209,9 +13206,9 @@ function parseQuery(query) {
|
|
|
11209
13206
|
}
|
|
11210
13207
|
|
|
11211
13208
|
// ../../b4m-core/packages/services/dist/src/llm/tools/implementation/editLocalFile/index.js
|
|
11212
|
-
import { promises as
|
|
11213
|
-
import { existsSync as
|
|
11214
|
-
import
|
|
13209
|
+
import { promises as fs11 } from "fs";
|
|
13210
|
+
import { existsSync as existsSync9 } from "fs";
|
|
13211
|
+
import path14 from "path";
|
|
11215
13212
|
import { diffLines as diffLines3 } from "diff";
|
|
11216
13213
|
function generateDiff(original, modified) {
|
|
11217
13214
|
const differences = diffLines3(original, modified);
|
|
@@ -11235,16 +13232,16 @@ function generateDiff(original, modified) {
|
|
|
11235
13232
|
}
|
|
11236
13233
|
async function editLocalFile(params) {
|
|
11237
13234
|
const { path: filePath, old_string, new_string } = params;
|
|
11238
|
-
const normalizedPath =
|
|
11239
|
-
const resolvedPath =
|
|
11240
|
-
const cwd =
|
|
13235
|
+
const normalizedPath = path14.normalize(filePath);
|
|
13236
|
+
const resolvedPath = path14.resolve(process.cwd(), normalizedPath);
|
|
13237
|
+
const cwd = path14.resolve(process.cwd());
|
|
11241
13238
|
if (!resolvedPath.startsWith(cwd)) {
|
|
11242
13239
|
throw new Error(`Access denied: Cannot edit files outside of current working directory`);
|
|
11243
13240
|
}
|
|
11244
|
-
if (!
|
|
13241
|
+
if (!existsSync9(resolvedPath)) {
|
|
11245
13242
|
throw new Error(`File not found: ${filePath}`);
|
|
11246
13243
|
}
|
|
11247
|
-
const currentContent = await
|
|
13244
|
+
const currentContent = await fs11.readFile(resolvedPath, "utf-8");
|
|
11248
13245
|
if (!currentContent.includes(old_string)) {
|
|
11249
13246
|
const preview = old_string.length > 100 ? old_string.substring(0, 100) + "..." : old_string;
|
|
11250
13247
|
throw new Error(`String to replace not found in file. Make sure the old_string matches exactly (including whitespace and line endings). Searched for: "${preview}"`);
|
|
@@ -11254,7 +13251,7 @@ async function editLocalFile(params) {
|
|
|
11254
13251
|
throw new Error(`Found ${occurrences} occurrences of the string to replace. Please provide a more specific old_string that matches exactly one location.`);
|
|
11255
13252
|
}
|
|
11256
13253
|
const newContent = currentContent.replace(old_string, new_string);
|
|
11257
|
-
await
|
|
13254
|
+
await fs11.writeFile(resolvedPath, newContent, "utf-8");
|
|
11258
13255
|
const diffResult = generateDiff(old_string, new_string);
|
|
11259
13256
|
return `File edited successfully: ${filePath}
|
|
11260
13257
|
Changes: +${diffResult.additions} lines, -${diffResult.deletions} lines
|
|
@@ -11492,7 +13489,7 @@ async function getFileStats(files, since, filterPath) {
|
|
|
11492
13489
|
});
|
|
11493
13490
|
});
|
|
11494
13491
|
}
|
|
11495
|
-
function
|
|
13492
|
+
function formatResult3(result) {
|
|
11496
13493
|
const parts = [];
|
|
11497
13494
|
if (result.error) {
|
|
11498
13495
|
parts.push(`Error: ${result.error}`);
|
|
@@ -11567,7 +13564,7 @@ var recentChangesTool = {
|
|
|
11567
13564
|
}
|
|
11568
13565
|
try {
|
|
11569
13566
|
const result = await getRecentChanges(params);
|
|
11570
|
-
const formattedResult =
|
|
13567
|
+
const formattedResult = formatResult3(result);
|
|
11571
13568
|
context.logger.info("RecentChanges: Retrieved file changes", {
|
|
11572
13569
|
totalFiles: result.totalFiles,
|
|
11573
13570
|
displayedFiles: result.files.length,
|
|
@@ -11665,8 +13662,14 @@ var b4mTools = {
|
|
|
11665
13662
|
sunrise_sunset: sunriseSunsetTool,
|
|
11666
13663
|
iss_tracker: issTrackerTool,
|
|
11667
13664
|
planet_visibility: planetVisibilityTool,
|
|
11668
|
-
// Knowledge base
|
|
11669
|
-
search_knowledge_base: knowledgeBaseSearchTool
|
|
13665
|
+
// Knowledge base tools
|
|
13666
|
+
search_knowledge_base: knowledgeBaseSearchTool,
|
|
13667
|
+
retrieve_knowledge_content: knowledgeBaseRetrieveTool,
|
|
13668
|
+
// Quantum optimization tools
|
|
13669
|
+
quantum_schedule: quantumScheduleTool,
|
|
13670
|
+
quantum_formulate: quantumFormulateTool,
|
|
13671
|
+
// Navigation tool
|
|
13672
|
+
navigate_view: navigateViewTool
|
|
11670
13673
|
};
|
|
11671
13674
|
var cliOnlyTools = {
|
|
11672
13675
|
// File operation tools
|
|
@@ -11801,54 +13804,258 @@ var generateMcpTools = async (mcpData) => {
|
|
|
11801
13804
|
return result;
|
|
11802
13805
|
};
|
|
11803
13806
|
|
|
13807
|
+
// ../../b4m-core/packages/services/dist/src/llm/agents/ServerSubagentOrchestrator.js
|
|
13808
|
+
var SUBAGENT_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
13809
|
+
|
|
13810
|
+
// ../../b4m-core/packages/services/dist/src/llm/agents/CodeReviewAgent.js
|
|
13811
|
+
var CodeReviewAgent = (config) => ({
|
|
13812
|
+
name: "code_review",
|
|
13813
|
+
description: "Code review specialist for analyzing code quality, bugs, and improvements",
|
|
13814
|
+
model: config?.model ?? "claude-sonnet-4-5-20250929",
|
|
13815
|
+
defaultThoroughness: config?.defaultThoroughness ?? "medium",
|
|
13816
|
+
maxIterations: { quick: 3, medium: 8, very_thorough: 15 },
|
|
13817
|
+
deniedTools: ["image_generation", "edit_image", "delegate_to_agent", ...config?.extraDeniedTools ?? []],
|
|
13818
|
+
allowedTools: config?.extraAllowedTools,
|
|
13819
|
+
systemPrompt: `You are a code review specialist. Your job is to analyze code for quality, correctness, security, and maintainability.
|
|
13820
|
+
|
|
13821
|
+
## Focus Areas
|
|
13822
|
+
- Bugs, logic errors, and edge cases
|
|
13823
|
+
- Security vulnerabilities (injection, auth issues, data exposure)
|
|
13824
|
+
- Code quality and readability
|
|
13825
|
+
- Performance concerns
|
|
13826
|
+
- Adherence to project patterns and conventions
|
|
13827
|
+
|
|
13828
|
+
## Review Process
|
|
13829
|
+
1. Understand the context and intent of the code changes
|
|
13830
|
+
2. Check for bugs, logic errors, and unhandled edge cases
|
|
13831
|
+
3. Identify security vulnerabilities and data handling issues
|
|
13832
|
+
4. Evaluate code clarity, naming, and structure
|
|
13833
|
+
5. Look for performance problems or unnecessary complexity
|
|
13834
|
+
|
|
13835
|
+
## Output Format
|
|
13836
|
+
Provide actionable feedback:
|
|
13837
|
+
|
|
13838
|
+
### Critical Issues
|
|
13839
|
+
- Bugs, security vulnerabilities, or correctness problems that must be fixed
|
|
13840
|
+
|
|
13841
|
+
### Suggestions
|
|
13842
|
+
- Code quality improvements with rationale
|
|
13843
|
+
|
|
13844
|
+
### Positive Observations
|
|
13845
|
+
- Well-implemented patterns worth noting (optional)
|
|
13846
|
+
|
|
13847
|
+
Focus on actionable, specific feedback referencing exact code locations. Your review will be used by the main agent.`
|
|
13848
|
+
});
|
|
13849
|
+
|
|
13850
|
+
// ../../b4m-core/packages/services/dist/src/llm/agents/ProjectManagerAgent.js
|
|
13851
|
+
var ProjectManagerAgent = (config) => ({
|
|
13852
|
+
name: "project_manager",
|
|
13853
|
+
description: "Project management via Jira and Confluence (create issues, search, update status, write docs). ALWAYS delegate Jira/Confluence requests to this agent \u2014 you do not have direct access to these tools",
|
|
13854
|
+
model: config?.model ?? "claude-sonnet-4-5-20250929",
|
|
13855
|
+
defaultThoroughness: config?.defaultThoroughness ?? "medium",
|
|
13856
|
+
maxIterations: { quick: 3, medium: 8, very_thorough: 15 },
|
|
13857
|
+
allowedTools: [...config?.extraAllowedTools ?? []],
|
|
13858
|
+
deniedTools: [...config?.extraDeniedTools ?? []],
|
|
13859
|
+
exclusiveMcpServers: ["atlassian"],
|
|
13860
|
+
systemPrompt: `You are a project management specialist with access to Jira and Confluence. Your job is to help manage projects, issues, documentation, and team workflows.
|
|
13861
|
+
|
|
13862
|
+
## Capabilities
|
|
13863
|
+
|
|
13864
|
+
### Jira
|
|
13865
|
+
- Search for issues using JQL
|
|
13866
|
+
- Create, update, and transition issues
|
|
13867
|
+
- Add comments and manage watchers
|
|
13868
|
+
- List projects and issue types
|
|
13869
|
+
|
|
13870
|
+
### Confluence
|
|
13871
|
+
- Search for documentation
|
|
13872
|
+
- Create and update pages
|
|
13873
|
+
- Browse spaces and page hierarchies
|
|
13874
|
+
|
|
13875
|
+
## Best Practices
|
|
13876
|
+
1. When searching Jira, use precise JQL queries (e.g., \`project = PROJ AND status = "In Progress"\`)
|
|
13877
|
+
2. When creating issues, always check available issue types first with \`atlassian__jira_list_issue_types\`
|
|
13878
|
+
3. When updating issue status, use \`atlassian__jira_update_issue_transition\` with the target status name
|
|
13879
|
+
4. When creating Confluence pages, use the user's personal space if no space is specified
|
|
13880
|
+
5. Always confirm destructive operations (delete) with the user before proceeding
|
|
13881
|
+
|
|
13882
|
+
## Output Format
|
|
13883
|
+
Provide a clear summary of actions taken:
|
|
13884
|
+
1. What was done (created, updated, searched, etc.)
|
|
13885
|
+
2. Links or keys to relevant items (e.g., PROJ-123)
|
|
13886
|
+
3. Any issues or warnings encountered
|
|
13887
|
+
|
|
13888
|
+
Be precise with issue keys and project names. Your results will be used by the main agent.`
|
|
13889
|
+
});
|
|
13890
|
+
|
|
13891
|
+
// ../../b4m-core/packages/services/dist/src/llm/agents/GithubManagerAgent.js
|
|
13892
|
+
var GithubManagerAgent = (config) => ({
|
|
13893
|
+
name: "github_manager",
|
|
13894
|
+
description: "GitHub operations (issues, pull requests, code search, branches, workflows, reviews). ALWAYS delegate GitHub requests to this agent \u2014 you do not have direct access to these tools",
|
|
13895
|
+
model: config?.model ?? "claude-sonnet-4-5-20250929",
|
|
13896
|
+
defaultThoroughness: config?.defaultThoroughness ?? "medium",
|
|
13897
|
+
maxIterations: { quick: 3, medium: 8, very_thorough: 15 },
|
|
13898
|
+
allowedTools: [...config?.extraAllowedTools ?? []],
|
|
13899
|
+
deniedTools: [...config?.extraDeniedTools ?? []],
|
|
13900
|
+
exclusiveMcpServers: ["github"],
|
|
13901
|
+
systemPrompt: `You are a GitHub specialist with access to GitHub's API. Your job is to help manage repositories, issues, pull requests, code search, branches, and CI/CD workflows.
|
|
13902
|
+
|
|
13903
|
+
## First Step: ALWAYS Resolve Repository Context Automatically
|
|
13904
|
+
Before doing ANYTHING else, call the \`github__current_user\` tool. The response includes:
|
|
13905
|
+
- **selected_repositories**: The list of repositories the user has enabled for AI access (in \`owner/repo\` format)
|
|
13906
|
+
- **user**: The authenticated user's profile (login, name, etc.)
|
|
13907
|
+
|
|
13908
|
+
### Repository Resolution Rules (CRITICAL)
|
|
13909
|
+
You MUST use the selected_repositories list to automatically resolve the owner and repo. NEVER ask the user for the org, owner, or full repository path. Instead:
|
|
13910
|
+
|
|
13911
|
+
1. **Exact match**: If the user says a repo name that exactly matches a repo name in selected_repositories, use it immediately.
|
|
13912
|
+
2. **Partial/fuzzy match**: If the user provides a partial name, match it against any repo in selected_repositories whose name contains that term (e.g., if the user says "lumina" and selected_repositories contains \`SomeOrg/lumina5\`, use that).
|
|
13913
|
+
3. **Single repo shortcut**: If only one repository is selected, ALWAYS default to it \u2014 no questions asked.
|
|
13914
|
+
4. **Multiple matches**: Only if multiple selected repos match the user's term AND you truly cannot disambiguate, list the matching repos and ask which one. Do NOT list repos that don't match.
|
|
13915
|
+
5. **No match**: If nothing in selected_repositories matches, tell the user which repos are available and ask them to clarify.
|
|
13916
|
+
|
|
13917
|
+
**NEVER ask the user to "paste the GitHub URL" or provide the org/owner name.** You already have this information from \`github__current_user\`. The owner and repo are embedded in the \`owner/repo\` format of each selected repository entry.
|
|
13918
|
+
|
|
13919
|
+
### Defaults for Ambiguous Requests
|
|
13920
|
+
- **Timezone**: Default to UTC unless the user specifies otherwise.
|
|
13921
|
+
- **PR/Issue state**: Default to \`open\` unless the user specifies otherwise.
|
|
13922
|
+
- **Scope**: All matching repos unless the user narrows it down.
|
|
13923
|
+
- **When in doubt, act**: Prefer making reasonable assumptions and proceeding over asking clarifying questions. You can always note your assumptions in the response.
|
|
13924
|
+
|
|
13925
|
+
## Capabilities
|
|
13926
|
+
|
|
13927
|
+
### Issues
|
|
13928
|
+
- Create, update, and search issues
|
|
13929
|
+
- Add comments and manage labels
|
|
13930
|
+
- List and filter issues by state, assignee, labels
|
|
13931
|
+
|
|
13932
|
+
### Pull Requests
|
|
13933
|
+
- Create, update, and list pull requests
|
|
13934
|
+
- Get PR diffs, files changed, and review status
|
|
13935
|
+
- Merge pull requests and manage reviews
|
|
13936
|
+
- Request reviews from team members
|
|
13937
|
+
|
|
13938
|
+
### Code & Repository
|
|
13939
|
+
- Search code across repositories
|
|
13940
|
+
- Get file contents and commit history
|
|
13941
|
+
- Create and list branches and tags
|
|
13942
|
+
- Fork repositories
|
|
13943
|
+
|
|
13944
|
+
### CI/CD & Workflows
|
|
13945
|
+
- List and monitor workflow runs
|
|
13946
|
+
- Get job logs for debugging failures
|
|
13947
|
+
- Re-run failed jobs or entire workflows
|
|
13948
|
+
- Download workflow artifacts
|
|
13949
|
+
|
|
13950
|
+
### Notifications
|
|
13951
|
+
- List and manage GitHub notifications
|
|
13952
|
+
- Mark notifications as read/done
|
|
13953
|
+
|
|
13954
|
+
## Best Practices
|
|
13955
|
+
1. When searching issues or PRs, use GitHub search syntax (e.g., \`is:open label:bug assignee:username\`)
|
|
13956
|
+
2. When creating PRs, always include a clear title and description
|
|
13957
|
+
3. When reviewing PR changes, use \`get_pull_request_diff\` or \`get_pull_request_files\` for context
|
|
13958
|
+
4. For CI/CD debugging, use \`get_job_logs\` with \`failed_only=true\` to focus on failures
|
|
13959
|
+
5. Always include links to issues/PRs in your responses (e.g., owner/repo#123)
|
|
13960
|
+
|
|
13961
|
+
## Output Format
|
|
13962
|
+
Provide a clear summary of actions taken:
|
|
13963
|
+
1. What was done (created, searched, merged, etc.)
|
|
13964
|
+
2. Always provide links to relevant items (e.g., owner/repo#123, PR URLs)
|
|
13965
|
+
3. Any issues or warnings encountered
|
|
13966
|
+
|
|
13967
|
+
Be precise with repository names, issue numbers, and PR numbers. Your results will be used by the main agent.`
|
|
13968
|
+
});
|
|
13969
|
+
|
|
13970
|
+
// ../../b4m-core/packages/services/dist/src/llm/agents/ServerAgentStore.js
|
|
13971
|
+
var ServerAgentStore = class {
|
|
13972
|
+
constructor() {
|
|
13973
|
+
const builtInAgents = [
|
|
13974
|
+
// For now disable explore and plan agents as it is much faster if the parent llm handles this.
|
|
13975
|
+
// ExploreAgent(),
|
|
13976
|
+
// PlanAgent(),
|
|
13977
|
+
CodeReviewAgent(),
|
|
13978
|
+
ProjectManagerAgent(),
|
|
13979
|
+
GithubManagerAgent()
|
|
13980
|
+
];
|
|
13981
|
+
this.agents = new Map(builtInAgents.map((a) => [a.name, a]));
|
|
13982
|
+
}
|
|
13983
|
+
getAgent(name) {
|
|
13984
|
+
return this.agents.get(name);
|
|
13985
|
+
}
|
|
13986
|
+
getAllAgents() {
|
|
13987
|
+
return Array.from(this.agents.values());
|
|
13988
|
+
}
|
|
13989
|
+
getAgentNames() {
|
|
13990
|
+
return Array.from(this.agents.keys());
|
|
13991
|
+
}
|
|
13992
|
+
hasAgent(name) {
|
|
13993
|
+
return this.agents.has(name);
|
|
13994
|
+
}
|
|
13995
|
+
/**
|
|
13996
|
+
* Get the deduplicated list of MCP server names that are exclusive to agents.
|
|
13997
|
+
* Derived from each agent's `exclusiveMcpServers` field.
|
|
13998
|
+
*/
|
|
13999
|
+
getExclusiveMcpServers() {
|
|
14000
|
+
const servers = /* @__PURE__ */ new Set();
|
|
14001
|
+
for (const agent of this.agents.values()) {
|
|
14002
|
+
if (agent.exclusiveMcpServers) {
|
|
14003
|
+
for (const s of agent.exclusiveMcpServers) {
|
|
14004
|
+
servers.add(s);
|
|
14005
|
+
}
|
|
14006
|
+
}
|
|
14007
|
+
}
|
|
14008
|
+
return Array.from(servers);
|
|
14009
|
+
}
|
|
14010
|
+
};
|
|
14011
|
+
var serverAgentStore = new ServerAgentStore();
|
|
14012
|
+
|
|
11804
14013
|
// ../../b4m-core/packages/services/dist/src/llm/ChatCompletionProcess.js
|
|
11805
14014
|
import throttle2 from "lodash/throttle.js";
|
|
11806
14015
|
|
|
11807
14016
|
// ../../b4m-core/packages/services/dist/src/llm/ChatCompletionFeatures.js
|
|
11808
|
-
import { z as
|
|
14017
|
+
import { z as z140 } from "zod";
|
|
11809
14018
|
import uniq4 from "lodash/uniq.js";
|
|
11810
|
-
var QuestStartBodySchema =
|
|
11811
|
-
userId:
|
|
11812
|
-
sessionId:
|
|
11813
|
-
questId:
|
|
11814
|
-
message:
|
|
11815
|
-
messageFileIds:
|
|
11816
|
-
historyCount:
|
|
11817
|
-
fabFileIds:
|
|
14019
|
+
var QuestStartBodySchema = z140.object({
|
|
14020
|
+
userId: z140.string(),
|
|
14021
|
+
sessionId: z140.string(),
|
|
14022
|
+
questId: z140.string(),
|
|
14023
|
+
message: z140.string().min(1, "Message cannot be empty"),
|
|
14024
|
+
messageFileIds: z140.array(z140.string()),
|
|
14025
|
+
historyCount: z140.number(),
|
|
14026
|
+
fabFileIds: z140.array(z140.string()),
|
|
11818
14027
|
params: ChatCompletionCreateInputSchema,
|
|
11819
14028
|
dashboardParams: DashboardParamsSchema.optional(),
|
|
11820
|
-
enableQuestMaster:
|
|
11821
|
-
enableMementos:
|
|
11822
|
-
enableArtifacts:
|
|
11823
|
-
enableAgents:
|
|
11824
|
-
enableLattice:
|
|
14029
|
+
enableQuestMaster: z140.boolean().optional(),
|
|
14030
|
+
enableMementos: z140.boolean().optional(),
|
|
14031
|
+
enableArtifacts: z140.boolean().optional(),
|
|
14032
|
+
enableAgents: z140.boolean().optional(),
|
|
14033
|
+
enableLattice: z140.boolean().optional(),
|
|
11825
14034
|
promptMeta: PromptMetaZodSchema,
|
|
11826
|
-
tools:
|
|
11827
|
-
mcpServers:
|
|
11828
|
-
projectId:
|
|
11829
|
-
organizationId:
|
|
14035
|
+
tools: z140.array(z140.union([b4mLLMTools, z140.string()])).optional(),
|
|
14036
|
+
mcpServers: z140.array(z140.string()).optional(),
|
|
14037
|
+
projectId: z140.string().optional(),
|
|
14038
|
+
organizationId: z140.string().nullable().optional(),
|
|
11830
14039
|
questMaster: QuestMasterParamsSchema.optional(),
|
|
11831
|
-
toolPromptId:
|
|
14040
|
+
toolPromptId: z140.string().optional(),
|
|
11832
14041
|
researchMode: ResearchModeParamsSchema.optional(),
|
|
11833
|
-
fallbackModel:
|
|
11834
|
-
embeddingModel:
|
|
11835
|
-
queryComplexity:
|
|
14042
|
+
fallbackModel: z140.string().optional(),
|
|
14043
|
+
embeddingModel: z140.string().optional(),
|
|
14044
|
+
queryComplexity: z140.string(),
|
|
11836
14045
|
imageConfig: GenerateImageToolCallSchema.optional(),
|
|
11837
|
-
deepResearchConfig:
|
|
11838
|
-
maxDepth:
|
|
11839
|
-
duration:
|
|
14046
|
+
deepResearchConfig: z140.object({
|
|
14047
|
+
maxDepth: z140.number().optional(),
|
|
14048
|
+
duration: z140.number().optional(),
|
|
11840
14049
|
// Note: searchers are passed via ToolContext and not through this API schema
|
|
11841
|
-
searchers:
|
|
14050
|
+
searchers: z140.array(z140.any()).optional()
|
|
11842
14051
|
}).optional(),
|
|
11843
|
-
extraContextMessages:
|
|
11844
|
-
role:
|
|
11845
|
-
content:
|
|
11846
|
-
fabFileIds:
|
|
14052
|
+
extraContextMessages: z140.array(z140.object({
|
|
14053
|
+
role: z140.enum(["user", "assistant", "system", "function", "tool"]),
|
|
14054
|
+
content: z140.union([z140.string(), z140.array(z140.any())]),
|
|
14055
|
+
fabFileIds: z140.array(z140.string()).optional()
|
|
11847
14056
|
})).optional(),
|
|
11848
14057
|
/** User's timezone (IANA format, e.g., "America/New_York") */
|
|
11849
|
-
timezone:
|
|
11850
|
-
/** Pre-fetched API key table from invoke phase — avoids redundant DB call in process (#6616 P1-a) */
|
|
11851
|
-
apiKeyTable: z139.record(z139.string(), z139.string().nullable()).optional()
|
|
14058
|
+
timezone: z140.string().optional()
|
|
11852
14059
|
});
|
|
11853
14060
|
|
|
11854
14061
|
// ../../b4m-core/packages/services/dist/src/llm/StatusManager.js
|
|
@@ -12348,89 +14555,89 @@ var BUILT_IN_TOOL_SET = new Set(b4mLLMTools.options);
|
|
|
12348
14555
|
import axios8 from "axios";
|
|
12349
14556
|
import { fileTypeFromBuffer as fileTypeFromBuffer4 } from "file-type";
|
|
12350
14557
|
import { v4 as uuidv47 } from "uuid";
|
|
12351
|
-
import { z as
|
|
14558
|
+
import { z as z141 } from "zod";
|
|
12352
14559
|
import { fromZodError as fromZodError2 } from "zod-validation-error";
|
|
12353
14560
|
var ImageGenerationBodySchema = OpenAIImageGenerationInput.extend({
|
|
12354
|
-
sessionId:
|
|
12355
|
-
questId:
|
|
12356
|
-
userId:
|
|
12357
|
-
prompt:
|
|
12358
|
-
organizationId:
|
|
12359
|
-
safety_tolerance:
|
|
12360
|
-
prompt_upsampling:
|
|
12361
|
-
seed:
|
|
12362
|
-
output_format:
|
|
12363
|
-
width:
|
|
12364
|
-
height:
|
|
12365
|
-
aspect_ratio:
|
|
12366
|
-
fabFileIds:
|
|
14561
|
+
sessionId: z141.string(),
|
|
14562
|
+
questId: z141.string(),
|
|
14563
|
+
userId: z141.string(),
|
|
14564
|
+
prompt: z141.string(),
|
|
14565
|
+
organizationId: z141.string().nullable().optional(),
|
|
14566
|
+
safety_tolerance: z141.number().min(BFL_SAFETY_TOLERANCE.MIN).max(BFL_SAFETY_TOLERANCE.MAX).optional().default(BFL_SAFETY_TOLERANCE.DEFAULT),
|
|
14567
|
+
prompt_upsampling: z141.boolean().optional().default(false),
|
|
14568
|
+
seed: z141.number().nullable().optional(),
|
|
14569
|
+
output_format: z141.enum(["jpeg", "png"]).nullable().optional().default("png"),
|
|
14570
|
+
width: z141.number().optional(),
|
|
14571
|
+
height: z141.number().optional(),
|
|
14572
|
+
aspect_ratio: z141.string().optional(),
|
|
14573
|
+
fabFileIds: z141.array(z141.string()).optional()
|
|
12367
14574
|
});
|
|
12368
14575
|
|
|
12369
14576
|
// ../../b4m-core/packages/services/dist/src/llm/VideoGeneration.js
|
|
12370
14577
|
import axios9 from "axios";
|
|
12371
14578
|
import { v4 as uuidv48 } from "uuid";
|
|
12372
|
-
import { z as
|
|
14579
|
+
import { z as z142 } from "zod";
|
|
12373
14580
|
import { fromZodError as fromZodError3 } from "zod-validation-error";
|
|
12374
|
-
var VideoGenerationBodySchema =
|
|
12375
|
-
sessionId:
|
|
12376
|
-
questId:
|
|
12377
|
-
userId:
|
|
12378
|
-
prompt:
|
|
12379
|
-
model:
|
|
12380
|
-
seconds:
|
|
12381
|
-
size:
|
|
12382
|
-
organizationId:
|
|
14581
|
+
var VideoGenerationBodySchema = z142.object({
|
|
14582
|
+
sessionId: z142.string(),
|
|
14583
|
+
questId: z142.string(),
|
|
14584
|
+
userId: z142.string(),
|
|
14585
|
+
prompt: z142.string(),
|
|
14586
|
+
model: z142.nativeEnum(VideoModels).default(VideoModels.SORA_2),
|
|
14587
|
+
seconds: z142.union([z142.literal(4), z142.literal(8), z142.literal(12)]).default(4),
|
|
14588
|
+
size: z142.enum(["720x1280", "1280x720", "1024x1792", "1792x1024"]).default(VIDEO_SIZE_CONSTRAINTS.SORA.defaultSize),
|
|
14589
|
+
organizationId: z142.string().nullable().optional()
|
|
12383
14590
|
});
|
|
12384
14591
|
|
|
12385
14592
|
// ../../b4m-core/packages/services/dist/src/llm/ImageEdit.js
|
|
12386
14593
|
import axios10 from "axios";
|
|
12387
14594
|
import { fileTypeFromBuffer as fileTypeFromBuffer5 } from "file-type";
|
|
12388
14595
|
import { v4 as uuidv49 } from "uuid";
|
|
12389
|
-
import { z as
|
|
14596
|
+
import { z as z143 } from "zod";
|
|
12390
14597
|
import { fromZodError as fromZodError4 } from "zod-validation-error";
|
|
12391
|
-
var ImageEditBodySchema = OpenAIImageGenerationInput.extend({
|
|
12392
|
-
sessionId:
|
|
12393
|
-
questId:
|
|
12394
|
-
userId:
|
|
12395
|
-
prompt:
|
|
12396
|
-
organizationId:
|
|
12397
|
-
safety_tolerance:
|
|
12398
|
-
prompt_upsampling:
|
|
12399
|
-
seed:
|
|
12400
|
-
output_format:
|
|
12401
|
-
width:
|
|
12402
|
-
height:
|
|
12403
|
-
aspect_ratio:
|
|
12404
|
-
size:
|
|
12405
|
-
fabFileIds:
|
|
12406
|
-
image:
|
|
14598
|
+
var ImageEditBodySchema = OpenAIImageGenerationInput.extend({
|
|
14599
|
+
sessionId: z143.string(),
|
|
14600
|
+
questId: z143.string(),
|
|
14601
|
+
userId: z143.string(),
|
|
14602
|
+
prompt: z143.string(),
|
|
14603
|
+
organizationId: z143.string().nullable().optional(),
|
|
14604
|
+
safety_tolerance: z143.number().min(BFL_SAFETY_TOLERANCE.MIN).max(BFL_SAFETY_TOLERANCE.MAX).optional().default(BFL_SAFETY_TOLERANCE.DEFAULT),
|
|
14605
|
+
prompt_upsampling: z143.boolean().optional().default(false),
|
|
14606
|
+
seed: z143.number().nullable().optional(),
|
|
14607
|
+
output_format: z143.enum(["jpeg", "png"]).optional().default("png"),
|
|
14608
|
+
width: z143.number().optional(),
|
|
14609
|
+
height: z143.number().optional(),
|
|
14610
|
+
aspect_ratio: z143.string().optional(),
|
|
14611
|
+
size: z143.string().optional(),
|
|
14612
|
+
fabFileIds: z143.array(z143.string()).optional(),
|
|
14613
|
+
image: z143.string()
|
|
12407
14614
|
});
|
|
12408
14615
|
|
|
12409
14616
|
// ../../b4m-core/packages/services/dist/src/llm/refineText.js
|
|
12410
|
-
import { z as
|
|
12411
|
-
var refineTextLLMSchema =
|
|
12412
|
-
text:
|
|
12413
|
-
context:
|
|
14617
|
+
import { z as z144 } from "zod";
|
|
14618
|
+
var refineTextLLMSchema = z144.object({
|
|
14619
|
+
text: z144.string(),
|
|
14620
|
+
context: z144.string().optional()
|
|
12414
14621
|
// tone: z.enum(['formal', 'informal', 'neutral']).optional(),
|
|
12415
14622
|
// style: z.enum(['technical', 'creative', 'academic', 'business', 'narrative']).optional(),
|
|
12416
14623
|
});
|
|
12417
14624
|
|
|
12418
14625
|
// ../../b4m-core/packages/services/dist/src/llm/MementoEvaluationService.js
|
|
12419
|
-
import { z as
|
|
12420
|
-
var SingleMementoEvalSchema =
|
|
12421
|
-
importance:
|
|
14626
|
+
import { z as z145 } from "zod";
|
|
14627
|
+
var SingleMementoEvalSchema = z145.object({
|
|
14628
|
+
importance: z145.number().min(1).max(10),
|
|
12422
14629
|
// 1-10 scale for personal info importance
|
|
12423
|
-
summary:
|
|
12424
|
-
tags:
|
|
14630
|
+
summary: z145.string(),
|
|
14631
|
+
tags: z145.array(z145.string()).optional()
|
|
12425
14632
|
});
|
|
12426
|
-
var MementoEvalResponseSchema =
|
|
12427
|
-
isPersonal:
|
|
12428
|
-
mementos:
|
|
14633
|
+
var MementoEvalResponseSchema = z145.object({
|
|
14634
|
+
isPersonal: z145.boolean(),
|
|
14635
|
+
mementos: z145.array(SingleMementoEvalSchema).optional()
|
|
12429
14636
|
// Array of distinct personal information
|
|
12430
14637
|
});
|
|
12431
14638
|
|
|
12432
14639
|
// ../../b4m-core/packages/services/dist/src/llm/SmallLLMService.js
|
|
12433
|
-
import { z as
|
|
14640
|
+
import { z as z146 } from "zod";
|
|
12434
14641
|
|
|
12435
14642
|
// ../../b4m-core/packages/services/dist/src/auth/AccessTokenGeneratorService.js
|
|
12436
14643
|
import jwt2 from "jsonwebtoken";
|
|
@@ -12449,10 +14656,10 @@ var ToolErrorType;
|
|
|
12449
14656
|
// src/utils/diffPreview.ts
|
|
12450
14657
|
import * as Diff from "diff";
|
|
12451
14658
|
import { readFile } from "fs/promises";
|
|
12452
|
-
import { existsSync as
|
|
14659
|
+
import { existsSync as existsSync10 } from "fs";
|
|
12453
14660
|
async function generateFileDiffPreview(args) {
|
|
12454
14661
|
try {
|
|
12455
|
-
if (!
|
|
14662
|
+
if (!existsSync10(args.path)) {
|
|
12456
14663
|
const lines2 = args.content.split("\n");
|
|
12457
14664
|
const preview = lines2.slice(0, 20).join("\n");
|
|
12458
14665
|
const hasMore = lines2.length > 20;
|
|
@@ -12490,10 +14697,10 @@ ${diffLines4.join("\n")}`;
|
|
|
12490
14697
|
}
|
|
12491
14698
|
async function generateFileDeletePreview(args) {
|
|
12492
14699
|
try {
|
|
12493
|
-
if (!
|
|
14700
|
+
if (!existsSync10(args.path)) {
|
|
12494
14701
|
return `[File does not exist: ${args.path}]`;
|
|
12495
14702
|
}
|
|
12496
|
-
const stats = await import("fs/promises").then((
|
|
14703
|
+
const stats = await import("fs/promises").then((fs15) => fs15.stat(args.path));
|
|
12497
14704
|
return `[File will be deleted]
|
|
12498
14705
|
|
|
12499
14706
|
Path: ${args.path}
|
|
@@ -12546,6 +14753,10 @@ var ServerToolExecutor = class {
|
|
|
12546
14753
|
};
|
|
12547
14754
|
|
|
12548
14755
|
// src/llm/ToolRouter.ts
|
|
14756
|
+
var wsToolExecutor = null;
|
|
14757
|
+
function setWebSocketToolExecutor(executor) {
|
|
14758
|
+
wsToolExecutor = executor;
|
|
14759
|
+
}
|
|
12549
14760
|
var SERVER_TOOLS = ["weather_info", "web_search", "web_fetch"];
|
|
12550
14761
|
var LOCAL_TOOLS = [
|
|
12551
14762
|
"file_read",
|
|
@@ -12568,7 +14779,15 @@ function isLocalTool(toolName) {
|
|
|
12568
14779
|
}
|
|
12569
14780
|
async function executeTool(toolName, input, apiClient, localToolFn) {
|
|
12570
14781
|
if (isServerTool(toolName)) {
|
|
12571
|
-
|
|
14782
|
+
if (wsToolExecutor) {
|
|
14783
|
+
logger.debug(`[ToolRouter] Routing ${toolName} to server via WebSocket`);
|
|
14784
|
+
const result = await wsToolExecutor.execute(toolName, input);
|
|
14785
|
+
if (!result.success) {
|
|
14786
|
+
return `Error executing ${toolName}: ${result.error || "Tool execution failed"}`;
|
|
14787
|
+
}
|
|
14788
|
+
return typeof result.content === "string" ? result.content : JSON.stringify(result.content ?? "");
|
|
14789
|
+
}
|
|
14790
|
+
logger.debug(`[ToolRouter] Routing ${toolName} to server via HTTP`);
|
|
12572
14791
|
const executor = new ServerToolExecutor(apiClient);
|
|
12573
14792
|
return await executor.executeTool(toolName, input);
|
|
12574
14793
|
} else if (isLocalTool(toolName)) {
|
|
@@ -12666,7 +14885,7 @@ async function executeCommandHook(hook, context) {
|
|
|
12666
14885
|
}
|
|
12667
14886
|
}
|
|
12668
14887
|
var MAX_PATTERN_LENGTH = 200;
|
|
12669
|
-
function
|
|
14888
|
+
function matchesToolPattern2(toolName, pattern) {
|
|
12670
14889
|
if (pattern.length > MAX_PATTERN_LENGTH) {
|
|
12671
14890
|
console.warn(`Hook pattern exceeds max length (${MAX_PATTERN_LENGTH}), skipping: ${pattern.slice(0, 50)}...`);
|
|
12672
14891
|
return false;
|
|
@@ -12684,7 +14903,7 @@ async function executeHooks(hooks, context) {
|
|
|
12684
14903
|
}
|
|
12685
14904
|
const matchingHooks = [];
|
|
12686
14905
|
for (const matcher of hooks) {
|
|
12687
|
-
const shouldMatch = !matcher.matcher || !context.tool_name ||
|
|
14906
|
+
const shouldMatch = !matcher.matcher || !context.tool_name || matchesToolPattern2(context.tool_name, matcher.matcher);
|
|
12688
14907
|
if (shouldMatch) {
|
|
12689
14908
|
matchingHooks.push(...matcher.hooks);
|
|
12690
14909
|
}
|
|
@@ -12723,7 +14942,7 @@ function buildHookContext(params) {
|
|
|
12723
14942
|
}
|
|
12724
14943
|
|
|
12725
14944
|
// src/agents/types.ts
|
|
12726
|
-
import { z as
|
|
14945
|
+
import { z as z147 } from "zod";
|
|
12727
14946
|
var HookBlockedError = class extends Error {
|
|
12728
14947
|
constructor(toolName, reason) {
|
|
12729
14948
|
super(`Hook blocked execution of ${toolName}: ${reason || "No reason provided"}`);
|
|
@@ -12735,40 +14954,40 @@ var ALWAYS_DENIED_FOR_AGENTS = [
|
|
|
12735
14954
|
"agent_delegate"
|
|
12736
14955
|
// No agent chaining
|
|
12737
14956
|
];
|
|
12738
|
-
var CommandHookSchema =
|
|
12739
|
-
type:
|
|
12740
|
-
command:
|
|
12741
|
-
timeout:
|
|
14957
|
+
var CommandHookSchema = z147.object({
|
|
14958
|
+
type: z147.literal("command"),
|
|
14959
|
+
command: z147.string().min(1, "Command is required for command hooks"),
|
|
14960
|
+
timeout: z147.number().optional()
|
|
12742
14961
|
});
|
|
12743
|
-
var PromptHookSchema =
|
|
12744
|
-
type:
|
|
12745
|
-
prompt:
|
|
12746
|
-
timeout:
|
|
14962
|
+
var PromptHookSchema = z147.object({
|
|
14963
|
+
type: z147.literal("prompt"),
|
|
14964
|
+
prompt: z147.string().min(1, "Prompt is required for prompt hooks"),
|
|
14965
|
+
timeout: z147.number().optional()
|
|
12747
14966
|
});
|
|
12748
|
-
var HookDefinitionSchema =
|
|
12749
|
-
var HookMatcherSchema =
|
|
12750
|
-
matcher:
|
|
12751
|
-
hooks:
|
|
14967
|
+
var HookDefinitionSchema = z147.discriminatedUnion("type", [CommandHookSchema, PromptHookSchema]);
|
|
14968
|
+
var HookMatcherSchema = z147.object({
|
|
14969
|
+
matcher: z147.string().optional(),
|
|
14970
|
+
hooks: z147.array(HookDefinitionSchema)
|
|
12752
14971
|
});
|
|
12753
|
-
var AgentHooksSchema =
|
|
12754
|
-
PreToolUse:
|
|
12755
|
-
PostToolUse:
|
|
12756
|
-
PostToolUseFailure:
|
|
12757
|
-
Stop:
|
|
14972
|
+
var AgentHooksSchema = z147.object({
|
|
14973
|
+
PreToolUse: z147.array(HookMatcherSchema).optional(),
|
|
14974
|
+
PostToolUse: z147.array(HookMatcherSchema).optional(),
|
|
14975
|
+
PostToolUseFailure: z147.array(HookMatcherSchema).optional(),
|
|
14976
|
+
Stop: z147.array(HookMatcherSchema).optional()
|
|
12758
14977
|
}).optional();
|
|
12759
|
-
var AgentFrontmatterSchema =
|
|
12760
|
-
description:
|
|
12761
|
-
model:
|
|
12762
|
-
"allowed-tools":
|
|
12763
|
-
"denied-tools":
|
|
12764
|
-
skills:
|
|
12765
|
-
"max-iterations":
|
|
12766
|
-
quick:
|
|
12767
|
-
medium:
|
|
12768
|
-
very_thorough:
|
|
14978
|
+
var AgentFrontmatterSchema = z147.object({
|
|
14979
|
+
description: z147.string().min(1, "Agent description is required"),
|
|
14980
|
+
model: z147.string().optional(),
|
|
14981
|
+
"allowed-tools": z147.array(z147.string()).optional(),
|
|
14982
|
+
"denied-tools": z147.array(z147.string()).optional(),
|
|
14983
|
+
skills: z147.array(z147.string()).optional(),
|
|
14984
|
+
"max-iterations": z147.object({
|
|
14985
|
+
quick: z147.number().int().positive().optional(),
|
|
14986
|
+
medium: z147.number().int().positive().optional(),
|
|
14987
|
+
very_thorough: z147.number().int().positive().optional()
|
|
12769
14988
|
}).optional(),
|
|
12770
|
-
"default-thoroughness":
|
|
12771
|
-
variables:
|
|
14989
|
+
"default-thoroughness": z147.enum(["quick", "medium", "very_thorough"]).optional(),
|
|
14990
|
+
variables: z147.record(z147.string()).optional(),
|
|
12772
14991
|
hooks: AgentHooksSchema
|
|
12773
14992
|
});
|
|
12774
14993
|
var DEFAULT_MAX_ITERATIONS = {
|
|
@@ -12780,25 +14999,26 @@ var DEFAULT_AGENT_MODEL = "claude-3-5-haiku-20241022";
|
|
|
12780
14999
|
var DEFAULT_THOROUGHNESS = "medium";
|
|
12781
15000
|
|
|
12782
15001
|
// src/utils/toolsAdapter.ts
|
|
15002
|
+
import path15 from "path";
|
|
12783
15003
|
var NoOpStorage = class extends BaseStorage {
|
|
12784
15004
|
async upload(input, destination, options) {
|
|
12785
15005
|
return `/tmp/${destination}`;
|
|
12786
15006
|
}
|
|
12787
|
-
async download(
|
|
15007
|
+
async download(path21) {
|
|
12788
15008
|
throw new Error("Download not supported in CLI");
|
|
12789
15009
|
}
|
|
12790
|
-
async delete(
|
|
15010
|
+
async delete(path21) {
|
|
12791
15011
|
}
|
|
12792
|
-
async getSignedUrl(
|
|
12793
|
-
return `/tmp/${
|
|
15012
|
+
async getSignedUrl(path21) {
|
|
15013
|
+
return `/tmp/${path21}`;
|
|
12794
15014
|
}
|
|
12795
|
-
getPublicUrl(
|
|
12796
|
-
return `/tmp/${
|
|
15015
|
+
getPublicUrl(path21) {
|
|
15016
|
+
return `/tmp/${path21}`;
|
|
12797
15017
|
}
|
|
12798
|
-
async getPreview(
|
|
12799
|
-
return `/tmp/${
|
|
15018
|
+
async getPreview(path21) {
|
|
15019
|
+
return `/tmp/${path21}`;
|
|
12800
15020
|
}
|
|
12801
|
-
async getMetadata(
|
|
15021
|
+
async getMetadata(path21) {
|
|
12802
15022
|
return { size: 0, contentType: "application/octet-stream" };
|
|
12803
15023
|
}
|
|
12804
15024
|
};
|
|
@@ -12971,6 +15191,27 @@ function wrapToolWithHooks(tool, hooks, hookContext) {
|
|
|
12971
15191
|
}
|
|
12972
15192
|
};
|
|
12973
15193
|
}
|
|
15194
|
+
var CHECKPOINT_TOOLS = /* @__PURE__ */ new Set(["create_file", "edit_local_file", "delete_file"]);
|
|
15195
|
+
function wrapToolWithCheckpointing(tool, checkpointStore) {
|
|
15196
|
+
if (!checkpointStore || !CHECKPOINT_TOOLS.has(tool.toolSchema.name)) {
|
|
15197
|
+
return tool;
|
|
15198
|
+
}
|
|
15199
|
+
const originalFn = tool.toolFn;
|
|
15200
|
+
const toolName = tool.toolSchema.name;
|
|
15201
|
+
return {
|
|
15202
|
+
...tool,
|
|
15203
|
+
toolFn: async (args) => {
|
|
15204
|
+
const filePath = args?.path;
|
|
15205
|
+
if (filePath) {
|
|
15206
|
+
try {
|
|
15207
|
+
await checkpointStore.createCheckpoint(toolName, [filePath], `before-${toolName}-${path15.basename(filePath)}`);
|
|
15208
|
+
} catch {
|
|
15209
|
+
}
|
|
15210
|
+
}
|
|
15211
|
+
return originalFn(args);
|
|
15212
|
+
}
|
|
15213
|
+
};
|
|
15214
|
+
}
|
|
12974
15215
|
var TOOL_NAME_MAPPING = {
|
|
12975
15216
|
// Claude Code -> B4M
|
|
12976
15217
|
read: "file_read",
|
|
@@ -12995,7 +15236,7 @@ function normalizeToolName(toolName) {
|
|
|
12995
15236
|
}
|
|
12996
15237
|
return TOOL_NAME_MAPPING[toolName] || toolName;
|
|
12997
15238
|
}
|
|
12998
|
-
function generateCliTools(userId, llm, model, permissionManager, showPermissionPrompt, agentContext, configStore, apiClient, toolFilter) {
|
|
15239
|
+
function generateCliTools(userId, llm, model, permissionManager, showPermissionPrompt, agentContext, configStore, apiClient, toolFilter, checkpointStore) {
|
|
12999
15240
|
const logger2 = new CliLogger();
|
|
13000
15241
|
const storage = new NoOpStorage();
|
|
13001
15242
|
const user = {
|
|
@@ -13054,9 +15295,17 @@ function generateCliTools(userId, llm, model, permissionManager, showPermissionP
|
|
|
13054
15295
|
// imageProcessorLambdaName (not needed for CLI)
|
|
13055
15296
|
tools_to_generate
|
|
13056
15297
|
);
|
|
13057
|
-
let tools = Object.entries(toolsMap).map(
|
|
13058
|
-
|
|
13059
|
-
|
|
15298
|
+
let tools = Object.entries(toolsMap).map(([_, tool]) => {
|
|
15299
|
+
const permissionWrapped = wrapToolWithPermission(
|
|
15300
|
+
tool,
|
|
15301
|
+
permissionManager,
|
|
15302
|
+
showPermissionPrompt,
|
|
15303
|
+
agentContext,
|
|
15304
|
+
configStore,
|
|
15305
|
+
apiClient
|
|
15306
|
+
);
|
|
15307
|
+
return wrapToolWithCheckpointing(permissionWrapped, checkpointStore ?? null);
|
|
15308
|
+
});
|
|
13060
15309
|
if (toolFilter) {
|
|
13061
15310
|
const { allowedTools, deniedTools } = toolFilter;
|
|
13062
15311
|
const normalizedAllowed = allowedTools?.map(normalizeToolName);
|
|
@@ -13234,8 +15483,8 @@ function getEnvironmentName(configApiConfig) {
|
|
|
13234
15483
|
}
|
|
13235
15484
|
|
|
13236
15485
|
// src/utils/contextLoader.ts
|
|
13237
|
-
import * as
|
|
13238
|
-
import * as
|
|
15486
|
+
import * as fs12 from "fs";
|
|
15487
|
+
import * as path16 from "path";
|
|
13239
15488
|
import { homedir as homedir3 } from "os";
|
|
13240
15489
|
var CONTEXT_FILE_SIZE_LIMIT = 100 * 1024;
|
|
13241
15490
|
var PROJECT_CONTEXT_FILES = [
|
|
@@ -13256,9 +15505,9 @@ function formatFileSize2(bytes) {
|
|
|
13256
15505
|
return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
|
|
13257
15506
|
}
|
|
13258
15507
|
function tryReadContextFile(dir, filename, source) {
|
|
13259
|
-
const filePath =
|
|
15508
|
+
const filePath = path16.join(dir, filename);
|
|
13260
15509
|
try {
|
|
13261
|
-
const stats =
|
|
15510
|
+
const stats = fs12.lstatSync(filePath);
|
|
13262
15511
|
if (stats.isDirectory()) {
|
|
13263
15512
|
return null;
|
|
13264
15513
|
}
|
|
@@ -13272,7 +15521,7 @@ function tryReadContextFile(dir, filename, source) {
|
|
|
13272
15521
|
error: `${source === "global" ? "Global" : "Project"} ${filename} exceeds 100KB limit (${formatFileSize2(stats.size)})`
|
|
13273
15522
|
};
|
|
13274
15523
|
}
|
|
13275
|
-
const content =
|
|
15524
|
+
const content = fs12.readFileSync(filePath, "utf-8");
|
|
13276
15525
|
return {
|
|
13277
15526
|
filename,
|
|
13278
15527
|
content,
|
|
@@ -13324,7 +15573,7 @@ ${project.content}`;
|
|
|
13324
15573
|
}
|
|
13325
15574
|
async function loadContextFiles(projectDir) {
|
|
13326
15575
|
const errors = [];
|
|
13327
|
-
const globalDir =
|
|
15576
|
+
const globalDir = path16.join(homedir3(), ".bike4mind");
|
|
13328
15577
|
const projectDirectory = projectDir || process.cwd();
|
|
13329
15578
|
const [globalResult, projectResult] = await Promise.all([
|
|
13330
15579
|
Promise.resolve(findContextFile(globalDir, GLOBAL_CONTEXT_FILES, "global")),
|
|
@@ -13595,8 +15844,8 @@ function substituteArguments(template, args) {
|
|
|
13595
15844
|
// ../../b4m-core/packages/mcp/dist/src/client.js
|
|
13596
15845
|
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
13597
15846
|
import { Client as Client2 } from "@modelcontextprotocol/sdk/client/index.js";
|
|
13598
|
-
import
|
|
13599
|
-
import { existsSync as
|
|
15847
|
+
import path17 from "path";
|
|
15848
|
+
import { existsSync as existsSync11, readdirSync as readdirSync3 } from "fs";
|
|
13600
15849
|
var MCPClient = class {
|
|
13601
15850
|
// Note: This class handles MCP server communication with repository filtering
|
|
13602
15851
|
mcp;
|
|
@@ -13637,18 +15886,18 @@ var MCPClient = class {
|
|
|
13637
15886
|
const root = process.env.INIT_CWD || process.cwd();
|
|
13638
15887
|
const candidatePaths = [
|
|
13639
15888
|
// When running from SST Lambda with node_modules structure (copyFiles)
|
|
13640
|
-
|
|
15889
|
+
path17.join(root, `node_modules/@bike4mind/mcp/dist/src/${this.serverName}/index.js`),
|
|
13641
15890
|
// When running from SST Lambda deployed environment (/var/task)
|
|
13642
|
-
|
|
15891
|
+
path17.join(root, `b4m-core/packages/mcp/dist/src/${this.serverName}/index.js`),
|
|
13643
15892
|
// When running from SST Lambda (.sst/artifacts/mcpHandler-dev), navigate to monorepo root (3 levels up)
|
|
13644
|
-
|
|
15893
|
+
path17.join(root, `../../../b4m-core/packages/mcp/dist/src/${this.serverName}/index.js`),
|
|
13645
15894
|
// When running from packages/client (Next.js app), navigate to monorepo root (2 levels up)
|
|
13646
|
-
|
|
15895
|
+
path17.join(root, `../../b4m-core/packages/mcp/dist/src/${this.serverName}/index.js`),
|
|
13647
15896
|
// Original paths (backward compatibility)
|
|
13648
|
-
|
|
13649
|
-
|
|
15897
|
+
path17.join(root, `/b4m-core/packages/mcp/dist/src/${this.serverName}/index.js`),
|
|
15898
|
+
path17.join(root, "core", "mcp", "servers", this.serverName, "dist", "index.js")
|
|
13650
15899
|
];
|
|
13651
|
-
const serverScriptPath = candidatePaths.find((p) =>
|
|
15900
|
+
const serverScriptPath = candidatePaths.find((p) => existsSync11(p));
|
|
13652
15901
|
if (!serverScriptPath) {
|
|
13653
15902
|
const getDirectories = (source) => readdirSync3(source, { withFileTypes: true }).filter((dirent) => dirent.isDirectory()).map((dirent) => dirent.name);
|
|
13654
15903
|
console.error(`[MCP] Server script not found. Tried paths:`, candidatePaths);
|
|
@@ -14530,6 +16779,173 @@ var ServerLlmBackend = class {
|
|
|
14530
16779
|
}
|
|
14531
16780
|
};
|
|
14532
16781
|
|
|
16782
|
+
// src/llm/WebSocketLlmBackend.ts
|
|
16783
|
+
import { v4 as uuidv411 } from "uuid";
|
|
16784
|
+
function stripThinkingBlocks2(text) {
|
|
16785
|
+
return text.replace(/<think>[\s\S]*?<\/think>/g, "").trim();
|
|
16786
|
+
}
|
|
16787
|
+
var WebSocketLlmBackend = class {
|
|
16788
|
+
constructor(options) {
|
|
16789
|
+
this.wsManager = options.wsManager;
|
|
16790
|
+
this.apiClient = options.apiClient;
|
|
16791
|
+
this.currentModel = options.model;
|
|
16792
|
+
this.tokenGetter = options.tokenGetter;
|
|
16793
|
+
this.wsCompletionUrl = options.wsCompletionUrl;
|
|
16794
|
+
}
|
|
16795
|
+
/**
|
|
16796
|
+
* Send completion request via HTTP POST, receive streaming response via WebSocket.
|
|
16797
|
+
* Collects all streamed chunks, then calls callback once at completion
|
|
16798
|
+
* with the full accumulated content.
|
|
16799
|
+
*/
|
|
16800
|
+
async complete(model, messages, options, callback) {
|
|
16801
|
+
logger.debug(`[WebSocketLlmBackend] Starting complete() with model: ${model}`);
|
|
16802
|
+
if (options.abortSignal?.aborted) {
|
|
16803
|
+
logger.debug("[WebSocketLlmBackend] Request aborted before start");
|
|
16804
|
+
return;
|
|
16805
|
+
}
|
|
16806
|
+
if (!this.wsManager.isConnected) {
|
|
16807
|
+
throw new Error("WebSocket is not connected");
|
|
16808
|
+
}
|
|
16809
|
+
const requestId = uuidv411();
|
|
16810
|
+
return new Promise((resolve3, reject) => {
|
|
16811
|
+
const isVerbose = process.env.B4M_VERBOSE === "1";
|
|
16812
|
+
const isUltraVerbose = process.env.B4M_DEBUG_STREAM === "1";
|
|
16813
|
+
const streamLogger = new StreamLogger(logger, "WebSocketLlmBackend", isVerbose, isUltraVerbose);
|
|
16814
|
+
streamLogger.streamStart();
|
|
16815
|
+
let eventCount = 0;
|
|
16816
|
+
let accumulatedText = "";
|
|
16817
|
+
let lastUsageInfo = {};
|
|
16818
|
+
let toolsUsed = [];
|
|
16819
|
+
let thinkingBlocks = [];
|
|
16820
|
+
let settled = false;
|
|
16821
|
+
const settle = (action) => {
|
|
16822
|
+
if (settled) return;
|
|
16823
|
+
settled = true;
|
|
16824
|
+
this.wsManager.offRequest(requestId);
|
|
16825
|
+
this.wsManager.offDisconnect(onDisconnect);
|
|
16826
|
+
action();
|
|
16827
|
+
};
|
|
16828
|
+
const settleResolve = () => settle(() => resolve3());
|
|
16829
|
+
const settleReject = (err) => settle(() => reject(err));
|
|
16830
|
+
const onDisconnect = () => {
|
|
16831
|
+
logger.debug("[WebSocketLlmBackend] Connection dropped during completion");
|
|
16832
|
+
settleReject(new Error("WebSocket connection lost during completion"));
|
|
16833
|
+
};
|
|
16834
|
+
this.wsManager.onDisconnect(onDisconnect);
|
|
16835
|
+
if (options.abortSignal) {
|
|
16836
|
+
if (options.abortSignal.aborted) {
|
|
16837
|
+
settleResolve();
|
|
16838
|
+
return;
|
|
16839
|
+
}
|
|
16840
|
+
options.abortSignal.addEventListener(
|
|
16841
|
+
"abort",
|
|
16842
|
+
() => {
|
|
16843
|
+
logger.debug("[WebSocketLlmBackend] Abort signal received");
|
|
16844
|
+
settleResolve();
|
|
16845
|
+
},
|
|
16846
|
+
{ once: true }
|
|
16847
|
+
);
|
|
16848
|
+
}
|
|
16849
|
+
const updateUsage = (usage) => {
|
|
16850
|
+
if (usage) {
|
|
16851
|
+
lastUsageInfo = { inputTokens: usage.inputTokens, outputTokens: usage.outputTokens };
|
|
16852
|
+
}
|
|
16853
|
+
};
|
|
16854
|
+
this.wsManager.onRequest(requestId, (message) => {
|
|
16855
|
+
if (options.abortSignal?.aborted) return;
|
|
16856
|
+
const action = message.action;
|
|
16857
|
+
if (action === "cli_completion_chunk") {
|
|
16858
|
+
eventCount++;
|
|
16859
|
+
const chunk = message.chunk;
|
|
16860
|
+
streamLogger.onEvent(eventCount, JSON.stringify(chunk));
|
|
16861
|
+
const textChunk = chunk.text || "";
|
|
16862
|
+
if (textChunk) accumulatedText += textChunk;
|
|
16863
|
+
updateUsage(chunk.usage);
|
|
16864
|
+
if (chunk.type === "content") {
|
|
16865
|
+
streamLogger.onContent(eventCount, textChunk, accumulatedText);
|
|
16866
|
+
} else if (chunk.type === "tool_use") {
|
|
16867
|
+
streamLogger.onCriticalEvent(eventCount, "TOOL_USE", `tools: ${chunk.tools?.length}`);
|
|
16868
|
+
if (chunk.tools && chunk.tools.length > 0) toolsUsed = chunk.tools;
|
|
16869
|
+
if (chunk.thinking && chunk.thinking.length > 0) thinkingBlocks = chunk.thinking;
|
|
16870
|
+
}
|
|
16871
|
+
} else if (action === "cli_completion_done") {
|
|
16872
|
+
streamLogger.streamComplete(accumulatedText);
|
|
16873
|
+
const cleanedText = stripThinkingBlocks2(accumulatedText);
|
|
16874
|
+
if (!cleanedText && toolsUsed.length === 0) {
|
|
16875
|
+
settleResolve();
|
|
16876
|
+
return;
|
|
16877
|
+
}
|
|
16878
|
+
const info = {
|
|
16879
|
+
...lastUsageInfo,
|
|
16880
|
+
...toolsUsed.length > 0 && { toolsUsed },
|
|
16881
|
+
...thinkingBlocks.length > 0 && { thinking: thinkingBlocks }
|
|
16882
|
+
};
|
|
16883
|
+
callback([cleanedText], info).then(() => settleResolve()).catch((err) => settleReject(err));
|
|
16884
|
+
} else if (action === "cli_completion_error") {
|
|
16885
|
+
const errorMsg = message.error || "Server error";
|
|
16886
|
+
streamLogger.onCriticalEvent(eventCount, "ERROR", errorMsg);
|
|
16887
|
+
settleReject(new Error(errorMsg));
|
|
16888
|
+
}
|
|
16889
|
+
});
|
|
16890
|
+
const axiosInstance = this.apiClient.getAxiosInstance();
|
|
16891
|
+
axiosInstance.post(
|
|
16892
|
+
this.wsCompletionUrl,
|
|
16893
|
+
{
|
|
16894
|
+
requestId,
|
|
16895
|
+
model,
|
|
16896
|
+
messages,
|
|
16897
|
+
options: {
|
|
16898
|
+
temperature: options.temperature,
|
|
16899
|
+
maxTokens: options.maxTokens,
|
|
16900
|
+
stream: true,
|
|
16901
|
+
tools: options.tools || []
|
|
16902
|
+
}
|
|
16903
|
+
},
|
|
16904
|
+
{ signal: options.abortSignal }
|
|
16905
|
+
).catch((err) => {
|
|
16906
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
16907
|
+
settleReject(new Error(`HTTP request failed: ${msg}`));
|
|
16908
|
+
});
|
|
16909
|
+
});
|
|
16910
|
+
}
|
|
16911
|
+
/**
|
|
16912
|
+
* Get available models from server (REST call, not streaming).
|
|
16913
|
+
* Delegates to ApiClient -- same as ServerLlmBackend.
|
|
16914
|
+
*/
|
|
16915
|
+
async getModelInfo() {
|
|
16916
|
+
try {
|
|
16917
|
+
logger.debug("[WebSocketLlmBackend] Fetching models from /api/models");
|
|
16918
|
+
const response = await this.apiClient.get("/api/models");
|
|
16919
|
+
if (!response || typeof response !== "object" || !Array.isArray(response.models)) {
|
|
16920
|
+
logger.warn("[WebSocketLlmBackend] Invalid API response format, using fallback models");
|
|
16921
|
+
return this.getFallbackModels();
|
|
16922
|
+
}
|
|
16923
|
+
const filteredModels = response.models.filter(
|
|
16924
|
+
(model) => model.type === "text" && model.supportsTools === true
|
|
16925
|
+
);
|
|
16926
|
+
if (filteredModels.length === 0) {
|
|
16927
|
+
logger.warn("[WebSocketLlmBackend] No CLI-compatible models found, using fallback");
|
|
16928
|
+
return this.getFallbackModels();
|
|
16929
|
+
}
|
|
16930
|
+
logger.debug(`[WebSocketLlmBackend] Loaded ${filteredModels.length} models`);
|
|
16931
|
+
return filteredModels;
|
|
16932
|
+
} catch (error) {
|
|
16933
|
+
logger.warn(
|
|
16934
|
+
`[WebSocketLlmBackend] Failed to fetch models: ${error instanceof Error ? error.message : String(error)}`
|
|
16935
|
+
);
|
|
16936
|
+
return this.getFallbackModels();
|
|
16937
|
+
}
|
|
16938
|
+
}
|
|
16939
|
+
getFallbackModels() {
|
|
16940
|
+
return [
|
|
16941
|
+
{ id: "claude-sonnet-4-5-20250929", name: "Claude 4.5 Sonnet" },
|
|
16942
|
+
{ id: "claude-3-5-haiku-20241022", name: "Claude 3.5 Haiku" },
|
|
16943
|
+
{ id: "gpt-4o", name: "GPT-4o" },
|
|
16944
|
+
{ id: "gpt-4o-mini", name: "GPT-4o Mini" }
|
|
16945
|
+
];
|
|
16946
|
+
}
|
|
16947
|
+
};
|
|
16948
|
+
|
|
14533
16949
|
// src/llm/NotifyingLlmBackend.ts
|
|
14534
16950
|
var NotifyingLlmBackend = class {
|
|
14535
16951
|
constructor(inner, backgroundManager) {
|
|
@@ -14564,6 +16980,253 @@ Please acknowledge these background agent results and incorporate them into your
|
|
|
14564
16980
|
}
|
|
14565
16981
|
};
|
|
14566
16982
|
|
|
16983
|
+
// src/ws/WebSocketConnectionManager.ts
|
|
16984
|
+
var WebSocketConnectionManager = class {
|
|
16985
|
+
constructor(wsUrl, getToken) {
|
|
16986
|
+
this.ws = null;
|
|
16987
|
+
this.heartbeatInterval = null;
|
|
16988
|
+
this.reconnectAttempts = 0;
|
|
16989
|
+
this.maxReconnectDelay = 3e4;
|
|
16990
|
+
this.handlers = /* @__PURE__ */ new Map();
|
|
16991
|
+
this.disconnectHandlers = /* @__PURE__ */ new Set();
|
|
16992
|
+
this.reconnectTimer = null;
|
|
16993
|
+
this.connected = false;
|
|
16994
|
+
this.connecting = false;
|
|
16995
|
+
this.closed = false;
|
|
16996
|
+
this.wsUrl = wsUrl;
|
|
16997
|
+
this.getToken = getToken;
|
|
16998
|
+
}
|
|
16999
|
+
/**
|
|
17000
|
+
* Connect to the WebSocket server.
|
|
17001
|
+
* Resolves when connection is established, rejects on failure.
|
|
17002
|
+
*/
|
|
17003
|
+
async connect() {
|
|
17004
|
+
if (this.connected || this.connecting) return;
|
|
17005
|
+
this.connecting = true;
|
|
17006
|
+
const token = await this.getToken();
|
|
17007
|
+
if (!token) {
|
|
17008
|
+
this.connecting = false;
|
|
17009
|
+
throw new Error("No access token available for WebSocket connection");
|
|
17010
|
+
}
|
|
17011
|
+
return new Promise((resolve3, reject) => {
|
|
17012
|
+
logger.debug(`[WS] Connecting to ${this.wsUrl}...`);
|
|
17013
|
+
this.ws = new WebSocket(this.wsUrl, [`access_token.${token}`]);
|
|
17014
|
+
this.ws.onopen = () => {
|
|
17015
|
+
logger.debug("[WS] Connected");
|
|
17016
|
+
this.connected = true;
|
|
17017
|
+
this.connecting = false;
|
|
17018
|
+
this.reconnectAttempts = 0;
|
|
17019
|
+
this.startHeartbeat();
|
|
17020
|
+
resolve3();
|
|
17021
|
+
};
|
|
17022
|
+
this.ws.onmessage = (event) => {
|
|
17023
|
+
try {
|
|
17024
|
+
const data = typeof event.data === "string" ? event.data : event.data.toString();
|
|
17025
|
+
const message = JSON.parse(data);
|
|
17026
|
+
const requestId = message.requestId;
|
|
17027
|
+
if (requestId && this.handlers.has(requestId)) {
|
|
17028
|
+
this.handlers.get(requestId)(message);
|
|
17029
|
+
} else {
|
|
17030
|
+
logger.debug(`[WS] Unhandled message: ${message.action || "unknown"}`);
|
|
17031
|
+
}
|
|
17032
|
+
} catch (err) {
|
|
17033
|
+
logger.debug(`[WS] Failed to parse message: ${err}`);
|
|
17034
|
+
}
|
|
17035
|
+
};
|
|
17036
|
+
this.ws.onclose = () => {
|
|
17037
|
+
logger.debug("[WS] Connection closed");
|
|
17038
|
+
this.cleanup();
|
|
17039
|
+
this.notifyDisconnect();
|
|
17040
|
+
if (!this.closed) {
|
|
17041
|
+
this.scheduleReconnect();
|
|
17042
|
+
}
|
|
17043
|
+
};
|
|
17044
|
+
this.ws.onerror = (err) => {
|
|
17045
|
+
logger.debug(`[WS] Error: ${err}`);
|
|
17046
|
+
if (this.connecting) {
|
|
17047
|
+
this.connecting = false;
|
|
17048
|
+
this.connected = false;
|
|
17049
|
+
reject(new Error("WebSocket connection failed"));
|
|
17050
|
+
}
|
|
17051
|
+
};
|
|
17052
|
+
});
|
|
17053
|
+
}
|
|
17054
|
+
/** Whether the connection is currently established */
|
|
17055
|
+
get isConnected() {
|
|
17056
|
+
return this.connected;
|
|
17057
|
+
}
|
|
17058
|
+
/**
|
|
17059
|
+
* Send a JSON message over the WebSocket connection.
|
|
17060
|
+
*/
|
|
17061
|
+
send(data) {
|
|
17062
|
+
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
|
|
17063
|
+
throw new Error("WebSocket is not connected");
|
|
17064
|
+
}
|
|
17065
|
+
const payload = JSON.stringify(data);
|
|
17066
|
+
const sizeKB = (payload.length / 1024).toFixed(1);
|
|
17067
|
+
logger.debug(`[WS] Sending ${sizeKB} KB (action: ${data.action})`);
|
|
17068
|
+
if (payload.length > 32e3) {
|
|
17069
|
+
logger.warn(`[WS] Payload ${sizeKB} KB exceeds API Gateway 32 KB frame limit \u2014 connection will be closed`);
|
|
17070
|
+
}
|
|
17071
|
+
this.ws.send(payload);
|
|
17072
|
+
}
|
|
17073
|
+
/**
|
|
17074
|
+
* Register a handler for messages matching a specific requestId.
|
|
17075
|
+
*/
|
|
17076
|
+
onRequest(requestId, handler) {
|
|
17077
|
+
this.handlers.set(requestId, handler);
|
|
17078
|
+
}
|
|
17079
|
+
/**
|
|
17080
|
+
* Remove a handler for a specific requestId.
|
|
17081
|
+
*/
|
|
17082
|
+
offRequest(requestId) {
|
|
17083
|
+
this.handlers.delete(requestId);
|
|
17084
|
+
}
|
|
17085
|
+
/**
|
|
17086
|
+
* Register a handler that fires when the connection drops.
|
|
17087
|
+
*/
|
|
17088
|
+
onDisconnect(handler) {
|
|
17089
|
+
this.disconnectHandlers.add(handler);
|
|
17090
|
+
}
|
|
17091
|
+
/**
|
|
17092
|
+
* Remove a disconnect handler.
|
|
17093
|
+
*/
|
|
17094
|
+
offDisconnect(handler) {
|
|
17095
|
+
this.disconnectHandlers.delete(handler);
|
|
17096
|
+
}
|
|
17097
|
+
/**
|
|
17098
|
+
* Close the connection and stop all heartbeat/reconnect logic.
|
|
17099
|
+
*/
|
|
17100
|
+
disconnect() {
|
|
17101
|
+
this.closed = true;
|
|
17102
|
+
this.cleanup();
|
|
17103
|
+
if (this.ws) {
|
|
17104
|
+
this.ws.close();
|
|
17105
|
+
this.ws = null;
|
|
17106
|
+
}
|
|
17107
|
+
this.handlers.clear();
|
|
17108
|
+
this.disconnectHandlers.clear();
|
|
17109
|
+
}
|
|
17110
|
+
startHeartbeat() {
|
|
17111
|
+
this.stopHeartbeat();
|
|
17112
|
+
this.heartbeatInterval = setInterval(
|
|
17113
|
+
() => {
|
|
17114
|
+
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
|
|
17115
|
+
this.ws.send(JSON.stringify({ action: "heartbeat" }));
|
|
17116
|
+
logger.debug("[WS] Heartbeat sent");
|
|
17117
|
+
}
|
|
17118
|
+
},
|
|
17119
|
+
5 * 60 * 1e3
|
|
17120
|
+
);
|
|
17121
|
+
}
|
|
17122
|
+
stopHeartbeat() {
|
|
17123
|
+
if (this.heartbeatInterval) {
|
|
17124
|
+
clearInterval(this.heartbeatInterval);
|
|
17125
|
+
this.heartbeatInterval = null;
|
|
17126
|
+
}
|
|
17127
|
+
}
|
|
17128
|
+
cleanup() {
|
|
17129
|
+
this.connected = false;
|
|
17130
|
+
this.connecting = false;
|
|
17131
|
+
this.stopHeartbeat();
|
|
17132
|
+
if (this.reconnectTimer) {
|
|
17133
|
+
clearTimeout(this.reconnectTimer);
|
|
17134
|
+
this.reconnectTimer = null;
|
|
17135
|
+
}
|
|
17136
|
+
}
|
|
17137
|
+
notifyDisconnect() {
|
|
17138
|
+
for (const handler of this.disconnectHandlers) {
|
|
17139
|
+
try {
|
|
17140
|
+
handler();
|
|
17141
|
+
} catch {
|
|
17142
|
+
}
|
|
17143
|
+
}
|
|
17144
|
+
}
|
|
17145
|
+
scheduleReconnect() {
|
|
17146
|
+
if (this.closed) return;
|
|
17147
|
+
this.reconnectAttempts++;
|
|
17148
|
+
const delay = Math.min(1e3 * Math.pow(2, this.reconnectAttempts - 1), this.maxReconnectDelay);
|
|
17149
|
+
logger.debug(`[WS] Reconnecting in ${delay}ms (attempt ${this.reconnectAttempts})`);
|
|
17150
|
+
this.reconnectTimer = setTimeout(async () => {
|
|
17151
|
+
this.reconnectTimer = null;
|
|
17152
|
+
if (this.closed) return;
|
|
17153
|
+
try {
|
|
17154
|
+
await this.connect();
|
|
17155
|
+
} catch {
|
|
17156
|
+
logger.debug("[WS] Reconnection failed");
|
|
17157
|
+
}
|
|
17158
|
+
}, delay);
|
|
17159
|
+
}
|
|
17160
|
+
};
|
|
17161
|
+
|
|
17162
|
+
// src/ws/WebSocketToolExecutor.ts
|
|
17163
|
+
import { v4 as uuidv412 } from "uuid";
|
|
17164
|
+
var WebSocketToolExecutor = class {
|
|
17165
|
+
constructor(wsManager, tokenGetter) {
|
|
17166
|
+
this.wsManager = wsManager;
|
|
17167
|
+
this.tokenGetter = tokenGetter;
|
|
17168
|
+
}
|
|
17169
|
+
/**
|
|
17170
|
+
* Execute a server-side tool via WebSocket.
|
|
17171
|
+
* Returns the tool result or throws on error.
|
|
17172
|
+
*/
|
|
17173
|
+
async execute(toolName, input, abortSignal) {
|
|
17174
|
+
if (!this.wsManager.isConnected) {
|
|
17175
|
+
throw new Error("WebSocket is not connected");
|
|
17176
|
+
}
|
|
17177
|
+
const token = await this.tokenGetter();
|
|
17178
|
+
if (!token) {
|
|
17179
|
+
throw new Error("No access token available");
|
|
17180
|
+
}
|
|
17181
|
+
const requestId = uuidv412();
|
|
17182
|
+
return new Promise((resolve3, reject) => {
|
|
17183
|
+
let settled = false;
|
|
17184
|
+
const settle = (action) => {
|
|
17185
|
+
if (settled) return;
|
|
17186
|
+
settled = true;
|
|
17187
|
+
this.wsManager.offRequest(requestId);
|
|
17188
|
+
this.wsManager.offDisconnect(onDisconnect);
|
|
17189
|
+
action();
|
|
17190
|
+
};
|
|
17191
|
+
const settleResolve = (result) => settle(() => resolve3(result));
|
|
17192
|
+
const settleReject = (err) => settle(() => reject(err));
|
|
17193
|
+
const onDisconnect = () => {
|
|
17194
|
+
settleReject(new Error("WebSocket connection lost during tool execution"));
|
|
17195
|
+
};
|
|
17196
|
+
this.wsManager.onDisconnect(onDisconnect);
|
|
17197
|
+
if (abortSignal) {
|
|
17198
|
+
if (abortSignal.aborted) {
|
|
17199
|
+
settleReject(new Error("Tool execution aborted"));
|
|
17200
|
+
return;
|
|
17201
|
+
}
|
|
17202
|
+
abortSignal.addEventListener("abort", () => settleReject(new Error("Tool execution aborted")), {
|
|
17203
|
+
once: true
|
|
17204
|
+
});
|
|
17205
|
+
}
|
|
17206
|
+
this.wsManager.onRequest(requestId, (message) => {
|
|
17207
|
+
if (message.action === "cli_tool_response") {
|
|
17208
|
+
settleResolve({
|
|
17209
|
+
success: message.success,
|
|
17210
|
+
content: message.content,
|
|
17211
|
+
error: message.error
|
|
17212
|
+
});
|
|
17213
|
+
}
|
|
17214
|
+
});
|
|
17215
|
+
try {
|
|
17216
|
+
this.wsManager.send({
|
|
17217
|
+
action: "cli_tool_request",
|
|
17218
|
+
accessToken: token,
|
|
17219
|
+
requestId,
|
|
17220
|
+
toolName,
|
|
17221
|
+
input
|
|
17222
|
+
});
|
|
17223
|
+
} catch (err) {
|
|
17224
|
+
settleReject(err instanceof Error ? err : new Error(String(err)));
|
|
17225
|
+
}
|
|
17226
|
+
});
|
|
17227
|
+
}
|
|
17228
|
+
};
|
|
17229
|
+
|
|
14567
17230
|
// src/auth/ApiClient.ts
|
|
14568
17231
|
import axios11 from "axios";
|
|
14569
17232
|
var ApiClient = class {
|
|
@@ -14701,7 +17364,7 @@ import { isAxiosError as isAxiosError2 } from "axios";
|
|
|
14701
17364
|
// package.json
|
|
14702
17365
|
var package_default = {
|
|
14703
17366
|
name: "@bike4mind/cli",
|
|
14704
|
-
version: "0.2.
|
|
17367
|
+
version: "0.2.31-b4m-cli-undo-command.19493+44c80c9bc",
|
|
14705
17368
|
type: "module",
|
|
14706
17369
|
description: "Interactive CLI tool for Bike4Mind with ReAct agents",
|
|
14707
17370
|
license: "UNLICENSED",
|
|
@@ -14812,10 +17475,10 @@ var package_default = {
|
|
|
14812
17475
|
},
|
|
14813
17476
|
devDependencies: {
|
|
14814
17477
|
"@bike4mind/agents": "0.1.0",
|
|
14815
|
-
"@bike4mind/common": "2.52.
|
|
14816
|
-
"@bike4mind/mcp": "1.31.
|
|
14817
|
-
"@bike4mind/services": "2.50.
|
|
14818
|
-
"@bike4mind/utils": "2.7.
|
|
17478
|
+
"@bike4mind/common": "2.52.1-b4m-cli-undo-command.19493+44c80c9bc",
|
|
17479
|
+
"@bike4mind/mcp": "1.31.1-b4m-cli-undo-command.19493+44c80c9bc",
|
|
17480
|
+
"@bike4mind/services": "2.50.1-b4m-cli-undo-command.19493+44c80c9bc",
|
|
17481
|
+
"@bike4mind/utils": "2.7.1-b4m-cli-undo-command.19493+44c80c9bc",
|
|
14819
17482
|
"@types/better-sqlite3": "^7.6.13",
|
|
14820
17483
|
"@types/diff": "^5.0.9",
|
|
14821
17484
|
"@types/jsonwebtoken": "^9.0.4",
|
|
@@ -14836,29 +17499,29 @@ var package_default = {
|
|
|
14836
17499
|
optionalDependencies: {
|
|
14837
17500
|
"@vscode/ripgrep": "^1.17.0"
|
|
14838
17501
|
},
|
|
14839
|
-
gitHead: "
|
|
17502
|
+
gitHead: "44c80c9bcf8bf132a8b75c3635426a901adce99e"
|
|
14840
17503
|
};
|
|
14841
17504
|
|
|
14842
17505
|
// src/agents/toolFilter.ts
|
|
14843
|
-
function
|
|
17506
|
+
function matchesToolPattern3(toolName, pattern) {
|
|
14844
17507
|
const regexPattern = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*");
|
|
14845
17508
|
return new RegExp(`^${regexPattern}$`).test(toolName);
|
|
14846
17509
|
}
|
|
14847
|
-
function
|
|
14848
|
-
return patterns.some((pattern) =>
|
|
17510
|
+
function matchesAnyPattern2(toolName, patterns) {
|
|
17511
|
+
return patterns.some((pattern) => matchesToolPattern3(toolName, pattern));
|
|
14849
17512
|
}
|
|
14850
|
-
function
|
|
17513
|
+
function filterToolsByPatterns2(allTools, allowedPatterns, deniedPatterns) {
|
|
14851
17514
|
return allTools.filter((tool) => {
|
|
14852
17515
|
const toolName = tool.toolSchema.name;
|
|
14853
17516
|
if (deniedPatterns && deniedPatterns.length > 0) {
|
|
14854
|
-
if (
|
|
17517
|
+
if (matchesAnyPattern2(toolName, deniedPatterns)) {
|
|
14855
17518
|
return false;
|
|
14856
17519
|
}
|
|
14857
17520
|
}
|
|
14858
17521
|
if (!allowedPatterns || allowedPatterns.length === 0) {
|
|
14859
17522
|
return true;
|
|
14860
17523
|
}
|
|
14861
|
-
return
|
|
17524
|
+
return matchesAnyPattern2(toolName, allowedPatterns);
|
|
14862
17525
|
});
|
|
14863
17526
|
}
|
|
14864
17527
|
|
|
@@ -15127,9 +17790,12 @@ var SubagentOrchestrator = class {
|
|
|
15127
17790
|
this.deps.showPermissionPrompt,
|
|
15128
17791
|
agentContext,
|
|
15129
17792
|
this.deps.configStore,
|
|
15130
|
-
this.deps.apiClient
|
|
17793
|
+
this.deps.apiClient,
|
|
17794
|
+
void 0,
|
|
17795
|
+
// toolFilter (applied separately below)
|
|
17796
|
+
this.deps.checkpointStore
|
|
15131
17797
|
);
|
|
15132
|
-
const filteredTools =
|
|
17798
|
+
const filteredTools = filterToolsByPatterns2(allTools, toolFilter.allowedTools, toolFilter.deniedTools);
|
|
15133
17799
|
if (this.deps.customCommandStore) {
|
|
15134
17800
|
const skillTool = createSkillTool({
|
|
15135
17801
|
customCommandStore: this.deps.customCommandStore,
|
|
@@ -15280,8 +17946,8 @@ var SubagentOrchestrator = class {
|
|
|
15280
17946
|
};
|
|
15281
17947
|
|
|
15282
17948
|
// src/agents/AgentStore.ts
|
|
15283
|
-
import
|
|
15284
|
-
import
|
|
17949
|
+
import fs13 from "fs/promises";
|
|
17950
|
+
import path18 from "path";
|
|
15285
17951
|
import os2 from "os";
|
|
15286
17952
|
import matter2 from "gray-matter";
|
|
15287
17953
|
var FULL_MODEL_ID_PREFIXES = [
|
|
@@ -15430,10 +18096,10 @@ var AgentStore = class {
|
|
|
15430
18096
|
const root = projectRoot || process.cwd();
|
|
15431
18097
|
const home = os2.homedir();
|
|
15432
18098
|
this.builtinAgentsDir = builtinDir;
|
|
15433
|
-
this.globalB4MAgentsDir =
|
|
15434
|
-
this.globalClaudeAgentsDir =
|
|
15435
|
-
this.projectB4MAgentsDir =
|
|
15436
|
-
this.projectClaudeAgentsDir =
|
|
18099
|
+
this.globalB4MAgentsDir = path18.join(home, ".bike4mind", "agents");
|
|
18100
|
+
this.globalClaudeAgentsDir = path18.join(home, ".claude", "agents");
|
|
18101
|
+
this.projectB4MAgentsDir = path18.join(root, ".bike4mind", "agents");
|
|
18102
|
+
this.projectClaudeAgentsDir = path18.join(root, ".claude", "agents");
|
|
15437
18103
|
}
|
|
15438
18104
|
/**
|
|
15439
18105
|
* Load all agents from all directories
|
|
@@ -15457,7 +18123,7 @@ var AgentStore = class {
|
|
|
15457
18123
|
*/
|
|
15458
18124
|
async loadAgentsFromDirectory(directory, source) {
|
|
15459
18125
|
try {
|
|
15460
|
-
const stats = await
|
|
18126
|
+
const stats = await fs13.stat(directory);
|
|
15461
18127
|
if (!stats.isDirectory()) {
|
|
15462
18128
|
return;
|
|
15463
18129
|
}
|
|
@@ -15485,9 +18151,9 @@ var AgentStore = class {
|
|
|
15485
18151
|
async findAgentFiles(directory) {
|
|
15486
18152
|
const files = [];
|
|
15487
18153
|
try {
|
|
15488
|
-
const entries = await
|
|
18154
|
+
const entries = await fs13.readdir(directory, { withFileTypes: true });
|
|
15489
18155
|
for (const entry of entries) {
|
|
15490
|
-
const fullPath =
|
|
18156
|
+
const fullPath = path18.join(directory, entry.name);
|
|
15491
18157
|
if (entry.isDirectory()) {
|
|
15492
18158
|
const subFiles = await this.findAgentFiles(fullPath);
|
|
15493
18159
|
files.push(...subFiles);
|
|
@@ -15504,10 +18170,10 @@ var AgentStore = class {
|
|
|
15504
18170
|
* Parse a single agent markdown file
|
|
15505
18171
|
*/
|
|
15506
18172
|
async parseAgentFile(filePath, source) {
|
|
15507
|
-
const content = await
|
|
18173
|
+
const content = await fs13.readFile(filePath, "utf-8");
|
|
15508
18174
|
const { data: frontmatter, content: body } = matter2(content);
|
|
15509
18175
|
const parsed = AgentFrontmatterSchema.parse(frontmatter);
|
|
15510
|
-
const name =
|
|
18176
|
+
const name = path18.basename(filePath, ".md");
|
|
15511
18177
|
const modelInput = parsed.model || DEFAULT_AGENT_MODEL;
|
|
15512
18178
|
const resolution = resolveModelAlias(modelInput, name, filePath);
|
|
15513
18179
|
if (!resolution.resolved && resolution.warning) {
|
|
@@ -15587,16 +18253,16 @@ var AgentStore = class {
|
|
|
15587
18253
|
*/
|
|
15588
18254
|
async createAgentFile(name, isGlobal = false, useClaude = true) {
|
|
15589
18255
|
const targetDir = isGlobal ? useClaude ? this.globalClaudeAgentsDir : this.globalB4MAgentsDir : useClaude ? this.projectClaudeAgentsDir : this.projectB4MAgentsDir;
|
|
15590
|
-
const filePath =
|
|
18256
|
+
const filePath = path18.join(targetDir, `${name}.md`);
|
|
15591
18257
|
try {
|
|
15592
|
-
await
|
|
18258
|
+
await fs13.access(filePath);
|
|
15593
18259
|
throw new Error(`Agent file already exists: ${filePath}`);
|
|
15594
18260
|
} catch (error) {
|
|
15595
18261
|
if (error.code !== "ENOENT") {
|
|
15596
18262
|
throw error;
|
|
15597
18263
|
}
|
|
15598
18264
|
}
|
|
15599
|
-
await
|
|
18265
|
+
await fs13.mkdir(targetDir, { recursive: true });
|
|
15600
18266
|
const template = `---
|
|
15601
18267
|
description: ${name} agent description
|
|
15602
18268
|
model: claude-3-5-haiku-20241022
|
|
@@ -15631,7 +18297,7 @@ You are a ${name} specialist. Your job is to [describe primary task].
|
|
|
15631
18297
|
## Output Format
|
|
15632
18298
|
Describe the expected output format here.
|
|
15633
18299
|
`;
|
|
15634
|
-
await
|
|
18300
|
+
await fs13.writeFile(filePath, template, "utf-8");
|
|
15635
18301
|
return filePath;
|
|
15636
18302
|
}
|
|
15637
18303
|
/**
|
|
@@ -16284,7 +18950,7 @@ function createTodoStore(onUpdate) {
|
|
|
16284
18950
|
|
|
16285
18951
|
// src/tools/findDefinitionTool.ts
|
|
16286
18952
|
import { stat as stat3 } from "fs/promises";
|
|
16287
|
-
import
|
|
18953
|
+
import path19 from "path";
|
|
16288
18954
|
import { execFile as execFile2 } from "child_process";
|
|
16289
18955
|
import { promisify as promisify2 } from "util";
|
|
16290
18956
|
import { createRequire as createRequire2 } from "module";
|
|
@@ -16305,8 +18971,8 @@ var ALL_KEYWORDS = Object.values(KIND_KEYWORDS).flat();
|
|
|
16305
18971
|
function getRipgrepPath2() {
|
|
16306
18972
|
try {
|
|
16307
18973
|
const ripgrepPath = require3.resolve("@vscode/ripgrep");
|
|
16308
|
-
const ripgrepDir =
|
|
16309
|
-
return
|
|
18974
|
+
const ripgrepDir = path19.dirname(ripgrepPath);
|
|
18975
|
+
return path19.join(ripgrepDir, "..", "bin", "rg" + (process.platform === "win32" ? ".exe" : ""));
|
|
16310
18976
|
} catch {
|
|
16311
18977
|
throw new Error(
|
|
16312
18978
|
"ripgrep is not available. Please install @vscode/ripgrep: pnpm add @vscode/ripgrep --filter @bike4mind/services"
|
|
@@ -16314,10 +18980,10 @@ function getRipgrepPath2() {
|
|
|
16314
18980
|
}
|
|
16315
18981
|
}
|
|
16316
18982
|
function isPathWithinWorkspace2(targetPath, baseCwd) {
|
|
16317
|
-
const resolvedTarget =
|
|
16318
|
-
const resolvedBase =
|
|
16319
|
-
const relativePath =
|
|
16320
|
-
return !relativePath.startsWith("..") && !
|
|
18983
|
+
const resolvedTarget = path19.resolve(targetPath);
|
|
18984
|
+
const resolvedBase = path19.resolve(baseCwd);
|
|
18985
|
+
const relativePath = path19.relative(resolvedBase, resolvedTarget);
|
|
18986
|
+
return !relativePath.startsWith("..") && !path19.isAbsolute(relativePath);
|
|
16321
18987
|
}
|
|
16322
18988
|
function escapeRegex(str) {
|
|
16323
18989
|
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
@@ -16350,7 +19016,7 @@ async function findDefinitions(params) {
|
|
|
16350
19016
|
throw new Error("symbol_name is required");
|
|
16351
19017
|
}
|
|
16352
19018
|
const baseCwd = process.cwd();
|
|
16353
|
-
const targetDir = search_path ?
|
|
19019
|
+
const targetDir = search_path ? path19.resolve(baseCwd, search_path) : baseCwd;
|
|
16354
19020
|
if (!isPathWithinWorkspace2(targetDir, baseCwd)) {
|
|
16355
19021
|
throw new Error(`Path validation failed: "${search_path}" resolves outside the allowed workspace directory`);
|
|
16356
19022
|
}
|
|
@@ -16405,7 +19071,7 @@ async function findDefinitions(params) {
|
|
|
16405
19071
|
const lineText = match.data.lines.text.trimEnd();
|
|
16406
19072
|
if (isLikelyDefinition(lineText)) {
|
|
16407
19073
|
allMatches.push({
|
|
16408
|
-
filePath:
|
|
19074
|
+
filePath: path19.relative(targetDir, match.data.path.text) || path19.basename(match.data.path.text),
|
|
16409
19075
|
lineNumber: match.data.line_number,
|
|
16410
19076
|
line: lineText
|
|
16411
19077
|
});
|
|
@@ -16483,8 +19149,8 @@ function createFindDefinitionTool() {
|
|
|
16483
19149
|
}
|
|
16484
19150
|
|
|
16485
19151
|
// src/tools/getFileStructure/index.ts
|
|
16486
|
-
import { existsSync as
|
|
16487
|
-
import
|
|
19152
|
+
import { existsSync as existsSync12, promises as fs14, statSync as statSync6 } from "fs";
|
|
19153
|
+
import path20 from "path";
|
|
16488
19154
|
|
|
16489
19155
|
// src/tools/getFileStructure/formatter.ts
|
|
16490
19156
|
var SECTIONS = [
|
|
@@ -16521,35 +19187,35 @@ function formatSection(lines, title, items, format) {
|
|
|
16521
19187
|
}
|
|
16522
19188
|
|
|
16523
19189
|
// src/tools/getFileStructure/index.ts
|
|
16524
|
-
var
|
|
19190
|
+
var MAX_FILE_SIZE4 = 10 * 1024 * 1024;
|
|
16525
19191
|
function createGetFileStructureTool() {
|
|
16526
19192
|
return {
|
|
16527
19193
|
toolFn: async (value) => {
|
|
16528
19194
|
const params = value;
|
|
16529
19195
|
try {
|
|
16530
19196
|
const cwd = process.cwd();
|
|
16531
|
-
const resolvedPath =
|
|
19197
|
+
const resolvedPath = path20.resolve(cwd, params.path);
|
|
16532
19198
|
if (!resolvedPath.startsWith(cwd)) {
|
|
16533
19199
|
return "Error: Access denied - cannot read files outside of current working directory";
|
|
16534
19200
|
}
|
|
16535
|
-
if (!
|
|
19201
|
+
if (!existsSync12(resolvedPath)) {
|
|
16536
19202
|
return `Error: File not found: ${params.path}`;
|
|
16537
19203
|
}
|
|
16538
19204
|
const stats = statSync6(resolvedPath);
|
|
16539
19205
|
if (stats.isDirectory()) {
|
|
16540
19206
|
return `Error: Path is a directory, not a file: ${params.path}`;
|
|
16541
19207
|
}
|
|
16542
|
-
if (stats.size >
|
|
16543
|
-
return `Error: File too large (${(stats.size / 1024 / 1024).toFixed(2)}MB). Max: ${
|
|
19208
|
+
if (stats.size > MAX_FILE_SIZE4) {
|
|
19209
|
+
return `Error: File too large (${(stats.size / 1024 / 1024).toFixed(2)}MB). Max: ${MAX_FILE_SIZE4 / 1024 / 1024}MB`;
|
|
16544
19210
|
}
|
|
16545
|
-
const ext =
|
|
19211
|
+
const ext = path20.extname(resolvedPath).toLowerCase();
|
|
16546
19212
|
const { getLanguageForExtension, parseFileStructure, getSupportedLanguages } = await import("./treeSitterEngine-4SGFQDY3.js");
|
|
16547
19213
|
const languageId = getLanguageForExtension(ext);
|
|
16548
19214
|
if (!languageId) {
|
|
16549
19215
|
const supported = getSupportedLanguages();
|
|
16550
19216
|
return `Error: Unsupported file type "${ext}". Supported languages: ${supported.join(", ")}`;
|
|
16551
19217
|
}
|
|
16552
|
-
const sourceCode = await
|
|
19218
|
+
const sourceCode = await fs14.readFile(resolvedPath, "utf-8");
|
|
16553
19219
|
const lineCount = sourceCode.split("\n").length;
|
|
16554
19220
|
const items = await parseFileStructure(sourceCode, languageId);
|
|
16555
19221
|
return formatStructureOutput(params.path, items, stats.size, lineCount);
|
|
@@ -16610,7 +19276,9 @@ function CliApp() {
|
|
|
16610
19276
|
agentStore: null,
|
|
16611
19277
|
abortController: null,
|
|
16612
19278
|
contextContent: "",
|
|
16613
|
-
backgroundManager: null
|
|
19279
|
+
backgroundManager: null,
|
|
19280
|
+
wsManager: null,
|
|
19281
|
+
checkpointStore: null
|
|
16614
19282
|
});
|
|
16615
19283
|
const [isInitialized, setIsInitialized] = useState10(false);
|
|
16616
19284
|
const [initError, setInitError] = useState10(null);
|
|
@@ -16638,6 +19306,10 @@ function CliApp() {
|
|
|
16638
19306
|
})
|
|
16639
19307
|
);
|
|
16640
19308
|
}
|
|
19309
|
+
if (state.wsManager) {
|
|
19310
|
+
state.wsManager.disconnect();
|
|
19311
|
+
setWebSocketToolExecutor(null);
|
|
19312
|
+
}
|
|
16641
19313
|
if (state.agent) {
|
|
16642
19314
|
state.agent.removeAllListeners();
|
|
16643
19315
|
}
|
|
@@ -16656,7 +19328,7 @@ function CliApp() {
|
|
|
16656
19328
|
setTimeout(() => {
|
|
16657
19329
|
process.exit(0);
|
|
16658
19330
|
}, 100);
|
|
16659
|
-
}, [state.session, state.sessionStore, state.mcpManager, state.agent, state.imageStore]);
|
|
19331
|
+
}, [state.session, state.sessionStore, state.mcpManager, state.agent, state.imageStore, state.wsManager]);
|
|
16660
19332
|
useInput9((input, key) => {
|
|
16661
19333
|
if (key.escape) {
|
|
16662
19334
|
const store = useCliStore.getState();
|
|
@@ -16731,7 +19403,7 @@ function CliApp() {
|
|
|
16731
19403
|
if (!isAuthenticated) {
|
|
16732
19404
|
console.log("\u2139\uFE0F AI features disabled. Available commands: /login, /help, /config\n");
|
|
16733
19405
|
const minimalSession = {
|
|
16734
|
-
id:
|
|
19406
|
+
id: uuidv413(),
|
|
16735
19407
|
name: `Session ${(/* @__PURE__ */ new Date()).toLocaleString()}`,
|
|
16736
19408
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
16737
19409
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -16759,10 +19431,45 @@ function CliApp() {
|
|
|
16759
19431
|
console.log(`\u{1F30D} API Environment: ${envName} (${apiBaseURL})`);
|
|
16760
19432
|
}
|
|
16761
19433
|
const apiClient = new ApiClient(apiBaseURL, state.configStore);
|
|
16762
|
-
const
|
|
16763
|
-
|
|
16764
|
-
|
|
16765
|
-
}
|
|
19434
|
+
const tokenGetter = async () => {
|
|
19435
|
+
const tokens = await state.configStore.getAuthTokens();
|
|
19436
|
+
return tokens?.accessToken ?? null;
|
|
19437
|
+
};
|
|
19438
|
+
let wsManager = null;
|
|
19439
|
+
let llm;
|
|
19440
|
+
try {
|
|
19441
|
+
const serverConfig = await apiClient.get(
|
|
19442
|
+
"/api/settings/serverConfig"
|
|
19443
|
+
);
|
|
19444
|
+
const wsUrl = serverConfig?.websocketUrl;
|
|
19445
|
+
const wsCompletionUrl = serverConfig?.wsCompletionUrl;
|
|
19446
|
+
if (wsUrl && wsCompletionUrl) {
|
|
19447
|
+
wsManager = new WebSocketConnectionManager(wsUrl, tokenGetter);
|
|
19448
|
+
await wsManager.connect();
|
|
19449
|
+
const wsToolExecutor2 = new WebSocketToolExecutor(wsManager, tokenGetter);
|
|
19450
|
+
setWebSocketToolExecutor(wsToolExecutor2);
|
|
19451
|
+
llm = new WebSocketLlmBackend({
|
|
19452
|
+
wsManager,
|
|
19453
|
+
apiClient,
|
|
19454
|
+
model: config.defaultModel,
|
|
19455
|
+
tokenGetter,
|
|
19456
|
+
wsCompletionUrl
|
|
19457
|
+
});
|
|
19458
|
+
logger.debug("\u{1F50C} Using WebSocket transport (bypasses CloudFront timeout)");
|
|
19459
|
+
} else {
|
|
19460
|
+
throw new Error("No websocketUrl or wsCompletionUrl in server config");
|
|
19461
|
+
}
|
|
19462
|
+
} catch (wsError) {
|
|
19463
|
+
logger.debug(
|
|
19464
|
+
`[WS] WebSocket unavailable, using SSE fallback: ${wsError instanceof Error ? wsError.message : String(wsError)}`
|
|
19465
|
+
);
|
|
19466
|
+
wsManager = null;
|
|
19467
|
+
setWebSocketToolExecutor(null);
|
|
19468
|
+
llm = new ServerLlmBackend({
|
|
19469
|
+
apiClient,
|
|
19470
|
+
model: config.defaultModel
|
|
19471
|
+
});
|
|
19472
|
+
}
|
|
16766
19473
|
const models = await llm.getModelInfo();
|
|
16767
19474
|
if (models.length === 0) {
|
|
16768
19475
|
throw new Error("No models available from server.");
|
|
@@ -16775,7 +19482,7 @@ function CliApp() {
|
|
|
16775
19482
|
}
|
|
16776
19483
|
llm.currentModel = modelInfo.id;
|
|
16777
19484
|
const newSession = {
|
|
16778
|
-
id:
|
|
19485
|
+
id: uuidv413(),
|
|
16779
19486
|
name: `Session ${(/* @__PURE__ */ new Date()).toLocaleString()}`,
|
|
16780
19487
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
16781
19488
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -16825,6 +19532,12 @@ function CliApp() {
|
|
|
16825
19532
|
enqueuePermissionPrompt(prompt);
|
|
16826
19533
|
});
|
|
16827
19534
|
};
|
|
19535
|
+
const checkpointProjectDir = state.configStore.getProjectConfigDir() || process.cwd();
|
|
19536
|
+
const checkpointStore = new CheckpointStore(checkpointProjectDir);
|
|
19537
|
+
try {
|
|
19538
|
+
await checkpointStore.init(newSession.id);
|
|
19539
|
+
} catch {
|
|
19540
|
+
}
|
|
16828
19541
|
const agentContext = {
|
|
16829
19542
|
currentAgent: null,
|
|
16830
19543
|
observationQueue: []
|
|
@@ -16837,7 +19550,10 @@ function CliApp() {
|
|
|
16837
19550
|
promptFn,
|
|
16838
19551
|
agentContext,
|
|
16839
19552
|
state.configStore,
|
|
16840
|
-
apiClient
|
|
19553
|
+
apiClient,
|
|
19554
|
+
void 0,
|
|
19555
|
+
// toolFilter
|
|
19556
|
+
checkpointStore
|
|
16841
19557
|
);
|
|
16842
19558
|
startupLog.push(`\u{1F6E0}\uFE0F Loaded ${b4mTools2.length} B4M tool(s)`);
|
|
16843
19559
|
const mcpManager = new McpManager(config);
|
|
@@ -16868,7 +19584,8 @@ function CliApp() {
|
|
|
16868
19584
|
apiClient,
|
|
16869
19585
|
agentStore,
|
|
16870
19586
|
customCommandStore: state.customCommandStore,
|
|
16871
|
-
enableParallelToolExecution: config.preferences.enableParallelToolExecution === true
|
|
19587
|
+
enableParallelToolExecution: config.preferences.enableParallelToolExecution === true,
|
|
19588
|
+
checkpointStore
|
|
16872
19589
|
});
|
|
16873
19590
|
const backgroundManager = new BackgroundAgentManager(orchestrator);
|
|
16874
19591
|
backgroundManager.setOnStatusChange((job) => {
|
|
@@ -16989,8 +19706,12 @@ function CliApp() {
|
|
|
16989
19706
|
// Store agent store for agent management commands
|
|
16990
19707
|
contextContent: contextResult.mergedContent,
|
|
16991
19708
|
// Store raw context for compact instructions
|
|
16992
|
-
backgroundManager
|
|
19709
|
+
backgroundManager,
|
|
16993
19710
|
// Store for grouped notification turn tracking
|
|
19711
|
+
wsManager,
|
|
19712
|
+
// WebSocket connection manager (null if using SSE fallback)
|
|
19713
|
+
checkpointStore
|
|
19714
|
+
// File change checkpointing for undo/restore
|
|
16994
19715
|
}));
|
|
16995
19716
|
setStoreSession(newSession);
|
|
16996
19717
|
const bannerLines = [
|
|
@@ -17085,13 +19806,13 @@ function CliApp() {
|
|
|
17085
19806
|
messageContent = multimodalMessage.content;
|
|
17086
19807
|
}
|
|
17087
19808
|
const userMessage = {
|
|
17088
|
-
id:
|
|
19809
|
+
id: uuidv413(),
|
|
17089
19810
|
role: "user",
|
|
17090
19811
|
content: userMessageContent,
|
|
17091
19812
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
17092
19813
|
};
|
|
17093
19814
|
const pendingAssistantMessage = {
|
|
17094
|
-
id:
|
|
19815
|
+
id: uuidv413(),
|
|
17095
19816
|
role: "assistant",
|
|
17096
19817
|
content: "...",
|
|
17097
19818
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -17304,13 +20025,13 @@ function CliApp() {
|
|
|
17304
20025
|
userMessageContent = message;
|
|
17305
20026
|
}
|
|
17306
20027
|
const userMessage = {
|
|
17307
|
-
id:
|
|
20028
|
+
id: uuidv413(),
|
|
17308
20029
|
role: "user",
|
|
17309
20030
|
content: userMessageContent,
|
|
17310
20031
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
17311
20032
|
};
|
|
17312
20033
|
const pendingAssistantMessage = {
|
|
17313
|
-
id:
|
|
20034
|
+
id: uuidv413(),
|
|
17314
20035
|
role: "assistant",
|
|
17315
20036
|
content: "...",
|
|
17316
20037
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -17390,7 +20111,7 @@ function CliApp() {
|
|
|
17390
20111
|
const currentSession = useCliStore.getState().session;
|
|
17391
20112
|
if (currentSession) {
|
|
17392
20113
|
const cancelMessage = {
|
|
17393
|
-
id:
|
|
20114
|
+
id: uuidv413(),
|
|
17394
20115
|
role: "assistant",
|
|
17395
20116
|
content: "\u26A0\uFE0F Operation cancelled by user",
|
|
17396
20117
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -17435,7 +20156,7 @@ function CliApp() {
|
|
|
17435
20156
|
setState((prev) => ({ ...prev, abortController }));
|
|
17436
20157
|
try {
|
|
17437
20158
|
const pendingAssistantMessage = {
|
|
17438
|
-
id:
|
|
20159
|
+
id: uuidv413(),
|
|
17439
20160
|
role: "assistant",
|
|
17440
20161
|
content: "...",
|
|
17441
20162
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -17463,7 +20184,7 @@ function CliApp() {
|
|
|
17463
20184
|
const currentSession = useCliStore.getState().session;
|
|
17464
20185
|
if (!currentSession) return;
|
|
17465
20186
|
const continuationMessage = {
|
|
17466
|
-
id:
|
|
20187
|
+
id: uuidv413(),
|
|
17467
20188
|
role: "assistant",
|
|
17468
20189
|
content: "---\n\n**Background Agent Results:**\n\n" + result.finalAnswer,
|
|
17469
20190
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -17524,13 +20245,13 @@ function CliApp() {
|
|
|
17524
20245
|
isError = true;
|
|
17525
20246
|
}
|
|
17526
20247
|
const userMessage = {
|
|
17527
|
-
id:
|
|
20248
|
+
id: uuidv413(),
|
|
17528
20249
|
role: "user",
|
|
17529
20250
|
content: `$ ${command}`,
|
|
17530
20251
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
17531
20252
|
};
|
|
17532
20253
|
const assistantMessage = {
|
|
17533
|
-
id:
|
|
20254
|
+
id: uuidv413(),
|
|
17534
20255
|
role: "assistant",
|
|
17535
20256
|
content: isError ? `\u274C Error:
|
|
17536
20257
|
${output}` : output.trim() || "(no output)",
|
|
@@ -17683,6 +20404,10 @@ Available commands:
|
|
|
17683
20404
|
/exit - Exit the CLI
|
|
17684
20405
|
/clear - Start a new session
|
|
17685
20406
|
/rewind - Rewind conversation to a previous point
|
|
20407
|
+
/undo - Undo the last file change
|
|
20408
|
+
/checkpoints - List available file restore points
|
|
20409
|
+
/restore <n> - Restore files to a specific checkpoint
|
|
20410
|
+
/diff [n] - Show diff between current state and a checkpoint
|
|
17686
20411
|
/login - Authenticate with your B4M account
|
|
17687
20412
|
/logout - Clear authentication and sign out
|
|
17688
20413
|
/whoami - Show current authenticated user
|
|
@@ -17775,6 +20500,9 @@ Keyboard Shortcuts:
|
|
|
17775
20500
|
}
|
|
17776
20501
|
await logger.initialize(loadedSession.id);
|
|
17777
20502
|
logger.debug("=== Session Resumed ===");
|
|
20503
|
+
if (state.checkpointStore) {
|
|
20504
|
+
state.checkpointStore.setSessionId(loadedSession.id);
|
|
20505
|
+
}
|
|
17778
20506
|
setState((prev) => ({ ...prev, session: loadedSession }));
|
|
17779
20507
|
setStoreSession(loadedSession);
|
|
17780
20508
|
useCliStore.getState().clearPendingMessages();
|
|
@@ -17999,7 +20727,7 @@ Keyboard Shortcuts:
|
|
|
17999
20727
|
console.clear();
|
|
18000
20728
|
const model = state.session?.model || state.config?.defaultModel || "claude-sonnet";
|
|
18001
20729
|
const newSession = {
|
|
18002
|
-
id:
|
|
20730
|
+
id: uuidv413(),
|
|
18003
20731
|
name: `Session ${(/* @__PURE__ */ new Date()).toLocaleString()}`,
|
|
18004
20732
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
18005
20733
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -18013,6 +20741,9 @@ Keyboard Shortcuts:
|
|
|
18013
20741
|
};
|
|
18014
20742
|
await logger.initialize(newSession.id);
|
|
18015
20743
|
logger.debug("=== New Session Started via /clear ===");
|
|
20744
|
+
if (state.checkpointStore) {
|
|
20745
|
+
state.checkpointStore.setSessionId(newSession.id);
|
|
20746
|
+
}
|
|
18016
20747
|
setState((prev) => ({ ...prev, session: newSession }));
|
|
18017
20748
|
setStoreSession(newSession);
|
|
18018
20749
|
useCliStore.getState().clearPendingMessages();
|
|
@@ -18023,8 +20754,7 @@ Keyboard Shortcuts:
|
|
|
18023
20754
|
`);
|
|
18024
20755
|
break;
|
|
18025
20756
|
}
|
|
18026
|
-
case "rewind":
|
|
18027
|
-
case "undo": {
|
|
20757
|
+
case "rewind": {
|
|
18028
20758
|
if (!state.session) {
|
|
18029
20759
|
console.log("No active session to rewind");
|
|
18030
20760
|
return;
|
|
@@ -18083,6 +20813,103 @@ Keyboard Shortcuts:
|
|
|
18083
20813
|
}));
|
|
18084
20814
|
break;
|
|
18085
20815
|
}
|
|
20816
|
+
case "undo": {
|
|
20817
|
+
if (!state.checkpointStore) {
|
|
20818
|
+
console.log("Checkpointing not available.");
|
|
20819
|
+
return;
|
|
20820
|
+
}
|
|
20821
|
+
const undoCheckpoints = state.checkpointStore.listCheckpoints();
|
|
20822
|
+
if (undoCheckpoints.length === 0) {
|
|
20823
|
+
console.log("No checkpoints available. No file changes have been made yet.");
|
|
20824
|
+
return;
|
|
20825
|
+
}
|
|
20826
|
+
try {
|
|
20827
|
+
const restored = await state.checkpointStore.undoLast();
|
|
20828
|
+
console.log(`
|
|
20829
|
+
\u2705 Restored ${restored.filePaths.length} file(s) to state before: ${restored.name}`);
|
|
20830
|
+
for (const f of restored.filePaths) {
|
|
20831
|
+
console.log(` - ${f}`);
|
|
20832
|
+
}
|
|
20833
|
+
console.log("");
|
|
20834
|
+
} catch (err) {
|
|
20835
|
+
console.log(`Failed to undo: ${err instanceof Error ? err.message : String(err)}`);
|
|
20836
|
+
}
|
|
20837
|
+
break;
|
|
20838
|
+
}
|
|
20839
|
+
case "checkpoints": {
|
|
20840
|
+
if (!state.checkpointStore) {
|
|
20841
|
+
console.log("Checkpointing not available.");
|
|
20842
|
+
return;
|
|
20843
|
+
}
|
|
20844
|
+
const cpList = state.checkpointStore.listCheckpoints();
|
|
20845
|
+
if (cpList.length === 0) {
|
|
20846
|
+
console.log("No checkpoints yet. Checkpoints are created automatically before file changes.");
|
|
20847
|
+
return;
|
|
20848
|
+
}
|
|
20849
|
+
console.log("\nCheckpoints (most recent first):\n");
|
|
20850
|
+
cpList.forEach((cp, idx) => {
|
|
20851
|
+
const ageMs = Date.now() - new Date(cp.timestamp).getTime();
|
|
20852
|
+
const ageSec = Math.floor(ageMs / 1e3);
|
|
20853
|
+
let age;
|
|
20854
|
+
if (ageSec < 60) age = `${ageSec}s ago`;
|
|
20855
|
+
else if (ageSec < 3600) age = `${Math.floor(ageSec / 60)}m ago`;
|
|
20856
|
+
else age = `${Math.floor(ageSec / 3600)}h ago`;
|
|
20857
|
+
console.log(` ${idx + 1}. ${cp.name} (${age}) - ${cp.filePaths.length} file(s)`);
|
|
20858
|
+
});
|
|
20859
|
+
console.log("\nUse /restore <number> to restore, /diff <number> to see changes.\n");
|
|
20860
|
+
break;
|
|
20861
|
+
}
|
|
20862
|
+
case "restore": {
|
|
20863
|
+
if (!state.checkpointStore) {
|
|
20864
|
+
console.log("Checkpointing not available.");
|
|
20865
|
+
return;
|
|
20866
|
+
}
|
|
20867
|
+
const restoreTarget = args[0];
|
|
20868
|
+
if (!restoreTarget) {
|
|
20869
|
+
console.log("Usage: /restore <checkpoint-number>");
|
|
20870
|
+
console.log("Use /checkpoints to list available restore points.");
|
|
20871
|
+
return;
|
|
20872
|
+
}
|
|
20873
|
+
const restoreNum = parseInt(restoreTarget, 10);
|
|
20874
|
+
if (isNaN(restoreNum) || restoreNum < 1) {
|
|
20875
|
+
console.log("Please provide a valid checkpoint number. Use /checkpoints to list.");
|
|
20876
|
+
return;
|
|
20877
|
+
}
|
|
20878
|
+
try {
|
|
20879
|
+
const restoredCp = await state.checkpointStore.restoreCheckpoint(restoreNum);
|
|
20880
|
+
console.log(`
|
|
20881
|
+
\u2705 Restored to checkpoint: ${restoredCp.name}`);
|
|
20882
|
+
for (const f of restoredCp.filePaths) {
|
|
20883
|
+
console.log(` - ${f}`);
|
|
20884
|
+
}
|
|
20885
|
+
console.log("");
|
|
20886
|
+
} catch (err) {
|
|
20887
|
+
console.log(`Failed to restore: ${err instanceof Error ? err.message : String(err)}`);
|
|
20888
|
+
}
|
|
20889
|
+
break;
|
|
20890
|
+
}
|
|
20891
|
+
case "diff": {
|
|
20892
|
+
if (!state.checkpointStore) {
|
|
20893
|
+
console.log("Checkpointing not available.");
|
|
20894
|
+
return;
|
|
20895
|
+
}
|
|
20896
|
+
const diffTarget = args[0] ? parseInt(args[0], 10) : 1;
|
|
20897
|
+
if (isNaN(diffTarget) || diffTarget < 1) {
|
|
20898
|
+
console.log("Usage: /diff [checkpoint-number] (defaults to 1, most recent)");
|
|
20899
|
+
return;
|
|
20900
|
+
}
|
|
20901
|
+
try {
|
|
20902
|
+
const diffOutput = state.checkpointStore.getCheckpointDiff(diffTarget);
|
|
20903
|
+
if (!diffOutput.trim()) {
|
|
20904
|
+
console.log("No differences found between checkpoint and current state.");
|
|
20905
|
+
} else {
|
|
20906
|
+
console.log(diffOutput);
|
|
20907
|
+
}
|
|
20908
|
+
} catch (err) {
|
|
20909
|
+
console.log(`Failed to generate diff: ${err instanceof Error ? err.message : String(err)}`);
|
|
20910
|
+
}
|
|
20911
|
+
break;
|
|
20912
|
+
}
|
|
18086
20913
|
case "usage": {
|
|
18087
20914
|
const usageAuthTokens = await state.configStore.getAuthTokens();
|
|
18088
20915
|
if (!usageAuthTokens || new Date(usageAuthTokens.expiresAt) <= /* @__PURE__ */ new Date()) {
|
|
@@ -18504,9 +21331,9 @@ No usage data available for the last ${USAGE_DAYS} days.`);
|
|
|
18504
21331
|
return { ...prev, config: updatedConfig };
|
|
18505
21332
|
});
|
|
18506
21333
|
if (modelChanged && state.agent) {
|
|
18507
|
-
const
|
|
18508
|
-
if (
|
|
18509
|
-
|
|
21334
|
+
const backend = state.agent.context.llm;
|
|
21335
|
+
if (backend) {
|
|
21336
|
+
backend.currentModel = updatedConfig.defaultModel;
|
|
18510
21337
|
}
|
|
18511
21338
|
}
|
|
18512
21339
|
};
|