@bike4mind/cli 0.2.30-subagent-delegation.19188 → 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/{artifactExtractor-YSZB7U2T.js → artifactExtractor-Z6CL6QFN.js} +1 -1
- package/dist/{chunk-44RZA4RG.js → chunk-2LLA4MTN.js} +2 -2
- package/dist/{chunk-TVW4ZESU.js → chunk-BYXFQJYT.js} +10 -1
- package/dist/{chunk-ICLEQOH6.js → chunk-GE7Q64MS.js} +1570 -5
- package/dist/{chunk-FLA4PWXB.js → chunk-RI45VJW3.js} +262 -34
- package/dist/{chunk-YNQB2RM7.js → chunk-T67NGQW6.js} +2 -2
- package/dist/{chunk-PP5WVA27.js → chunk-ZOWCX4MQ.js} +2 -2
- package/dist/{create-NX7AO3LR.js → create-JNUW7ICC.js} +3 -3
- package/dist/index.js +3118 -503
- package/dist/{llmMarkdownGenerator-BYSKH6JZ.js → llmMarkdownGenerator-KGA4HTQN.js} +1 -1
- package/dist/{markdownGenerator-25VAIDTL.js → markdownGenerator-ERG7FI5H.js} +1 -1
- package/dist/{mementoService-HY2ZPJF2.js → mementoService-I56R5DNA.js} +3 -3
- package/dist/{src-EIUJ74FN.js → src-EMANOLFK.js} +12 -2
- package/dist/{src-3JKTTJ5U.js → src-IAR65K73.js} +59 -1
- package/dist/{store-FU6NDC2W.js → store-K5MB3SE7.js} +1 -1
- package/dist/{subtractCredits-XU7WMHLE.js → subtractCredits-3MEQF5CV.js} +3 -3
- package/package.json +9 -9
package/dist/index.js
CHANGED
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
getEffectiveApiKey,
|
|
6
6
|
getOpenWeatherKey,
|
|
7
7
|
getSerperKey
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-T67NGQW6.js";
|
|
9
9
|
import "./chunk-GQGOWACU.js";
|
|
10
10
|
import {
|
|
11
11
|
ConfigStore,
|
|
@@ -14,9 +14,9 @@ import {
|
|
|
14
14
|
import {
|
|
15
15
|
selectActiveBackgroundAgents,
|
|
16
16
|
useCliStore
|
|
17
|
-
} from "./chunk-
|
|
18
|
-
import "./chunk-
|
|
19
|
-
import "./chunk-
|
|
17
|
+
} from "./chunk-BYXFQJYT.js";
|
|
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";
|
|
@@ -120,10 +122,22 @@ import { Box as Box4, Text as Text5, useInput as useInput2 } from "ink";
|
|
|
120
122
|
// src/components/CustomTextInput.tsx
|
|
121
123
|
import React2, { useEffect, useRef, useState } from "react";
|
|
122
124
|
import { Text as Text2, useInput } from "ink";
|
|
125
|
+
|
|
126
|
+
// src/config/constants.ts
|
|
127
|
+
var USAGE_DAYS = 30;
|
|
128
|
+
var MODEL_NAME_COLUMN_WIDTH = 18;
|
|
129
|
+
var USAGE_CACHE_TTL = 5 * 60 * 1e3;
|
|
130
|
+
var PASTE_LINE_THRESHOLD = 5;
|
|
131
|
+
var MAX_PASTE_SIZE = 5e5;
|
|
132
|
+
var IMAGE_DETECTION_MAX_LENGTH = 500;
|
|
133
|
+
|
|
134
|
+
// src/components/CustomTextInput.tsx
|
|
123
135
|
function CustomTextInput({
|
|
124
136
|
value,
|
|
125
137
|
onChange,
|
|
126
138
|
onSubmit,
|
|
139
|
+
onPaste,
|
|
140
|
+
pasteIndicator,
|
|
127
141
|
placeholder = "",
|
|
128
142
|
showCursor = true,
|
|
129
143
|
disabled = false
|
|
@@ -275,6 +289,11 @@ function CustomTextInput({
|
|
|
275
289
|
}
|
|
276
290
|
}
|
|
277
291
|
if (key.backspace || key.delete) {
|
|
292
|
+
if (pasteIndicator) {
|
|
293
|
+
onChange("");
|
|
294
|
+
setCursorOffset(0);
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
278
297
|
if (cursorOffset > 0) {
|
|
279
298
|
const newValue = value.slice(0, cursorOffset - 1) + value.slice(cursorOffset);
|
|
280
299
|
onChange(newValue);
|
|
@@ -291,6 +310,20 @@ function CustomTextInput({
|
|
|
291
310
|
return;
|
|
292
311
|
}
|
|
293
312
|
if (!key.ctrl && !key.meta && input.length > 0) {
|
|
313
|
+
if (input.length > 1 && onPaste) {
|
|
314
|
+
const normalized = input.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
|
|
315
|
+
const lineCount = normalized.split("\n").length;
|
|
316
|
+
if (lineCount >= PASTE_LINE_THRESHOLD) {
|
|
317
|
+
onPaste(normalized);
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
if (pasteIndicator) {
|
|
322
|
+
const sanitizedInput2 = input === "\r" ? "\n" : input;
|
|
323
|
+
onChange(sanitizedInput2);
|
|
324
|
+
setCursorOffset(sanitizedInput2.length);
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
294
327
|
const sanitizedInput = input === "\r" ? "\n" : input;
|
|
295
328
|
const newValue = value.slice(0, cursorOffset) + sanitizedInput + value.slice(cursorOffset);
|
|
296
329
|
onChange(newValue);
|
|
@@ -299,6 +332,9 @@ function CustomTextInput({
|
|
|
299
332
|
},
|
|
300
333
|
{ isActive: !disabled }
|
|
301
334
|
);
|
|
335
|
+
if (pasteIndicator) {
|
|
336
|
+
return /* @__PURE__ */ React2.createElement(Text2, null, /* @__PURE__ */ React2.createElement(Text2, { color: "yellow" }, pasteIndicator), showCursor && /* @__PURE__ */ React2.createElement(Text2, { inverse: true }, " "));
|
|
337
|
+
}
|
|
302
338
|
const hasValue = value.length > 0;
|
|
303
339
|
if (!hasValue) {
|
|
304
340
|
return /* @__PURE__ */ React2.createElement(Text2, null, showCursor ? /* @__PURE__ */ React2.createElement(React2.Fragment, null, /* @__PURE__ */ React2.createElement(Text2, { inverse: true }, " "), /* @__PURE__ */ React2.createElement(Text2, { dimColor: true }, placeholder)) : /* @__PURE__ */ React2.createElement(Text2, { dimColor: true }, placeholder));
|
|
@@ -316,9 +352,23 @@ function CommandAutocomplete({ commands, selectedIndex }) {
|
|
|
316
352
|
if (commands.length === 0) {
|
|
317
353
|
return /* @__PURE__ */ React3.createElement(Box2, { marginLeft: 2, marginTop: 1 }, /* @__PURE__ */ React3.createElement(Text3, { dimColor: true }, "No matching commands"));
|
|
318
354
|
}
|
|
319
|
-
|
|
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;
|
|
320
370
|
const args = cmd.args ? ` ${cmd.args}` : "";
|
|
321
|
-
const isSelected =
|
|
371
|
+
const isSelected = actualIndex === selectedIndex;
|
|
322
372
|
const sourceIcon = cmd.source === "global" ? "\u{1F3E0} " : cmd.source === "project" ? "\u{1F4C1} " : cmd.source === "built-in" ? "\u{1F527} " : "";
|
|
323
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));
|
|
324
374
|
}));
|
|
@@ -679,8 +729,25 @@ var COMMANDS = [
|
|
|
679
729
|
},
|
|
680
730
|
{
|
|
681
731
|
name: "rewind",
|
|
682
|
-
description: "Rewind conversation to a previous point"
|
|
683
|
-
|
|
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]"
|
|
684
751
|
},
|
|
685
752
|
{
|
|
686
753
|
name: "project-config",
|
|
@@ -1041,8 +1108,11 @@ function InputPrompt({
|
|
|
1041
1108
|
onBashModeChange
|
|
1042
1109
|
}) {
|
|
1043
1110
|
const value = useCliStore((state) => state.inputValue);
|
|
1044
|
-
const
|
|
1045
|
-
const
|
|
1111
|
+
const setValue = useCliStore((state) => state.setInputValue);
|
|
1112
|
+
const pastedContent = useCliStore((state) => state.pastedContent);
|
|
1113
|
+
const pastedLineCount = useCliStore((state) => state.pastedLineCount);
|
|
1114
|
+
const setPastedContent = useCliStore((state) => state.setPastedContent);
|
|
1115
|
+
const clearPaste = useCliStore((state) => state.clearPaste);
|
|
1046
1116
|
const [selectedIndex, setSelectedIndex] = useState3(0);
|
|
1047
1117
|
const [historyIndex, setHistoryIndex] = useState3(-1);
|
|
1048
1118
|
const [tempInput, setTempInput] = useState3("");
|
|
@@ -1059,8 +1129,8 @@ function InputPrompt({
|
|
|
1059
1129
|
useEffect3(() => {
|
|
1060
1130
|
onBashModeChange?.(isBashMode);
|
|
1061
1131
|
}, [isBashMode, onBashModeChange]);
|
|
1062
|
-
const
|
|
1063
|
-
const
|
|
1132
|
+
const commandQuery = value.startsWith("/") ? value.slice(1) : "";
|
|
1133
|
+
const shouldShowCommandAutocomplete = value.startsWith("/") && !disabled && !fileAutocomplete?.active && !looksLikeFilePath(value) && !pastedContent && !commandQuery.includes(" ");
|
|
1064
1134
|
const filteredCommands = useMemo(() => {
|
|
1065
1135
|
if (!shouldShowCommandAutocomplete) return [];
|
|
1066
1136
|
return searchCommands(commandQuery, commands);
|
|
@@ -1166,7 +1236,14 @@ function InputPrompt({
|
|
|
1166
1236
|
}
|
|
1167
1237
|
};
|
|
1168
1238
|
const handleSubmit = (input) => {
|
|
1169
|
-
if (disabled
|
|
1239
|
+
if (disabled) return;
|
|
1240
|
+
if (pastedContent) {
|
|
1241
|
+
const fullContent = pastedContent;
|
|
1242
|
+
clearPaste();
|
|
1243
|
+
onSubmit(fullContent);
|
|
1244
|
+
return;
|
|
1245
|
+
}
|
|
1246
|
+
if (!input.trim()) return;
|
|
1170
1247
|
if (fileAutocomplete?.active && filteredFiles.length > 0) {
|
|
1171
1248
|
const selectedFile = filteredFiles[fileSelectedIndex];
|
|
1172
1249
|
if (selectedFile) {
|
|
@@ -1202,13 +1279,23 @@ function InputPrompt({
|
|
|
1202
1279
|
setHistoryIndex(-1);
|
|
1203
1280
|
setFileAutocomplete(null);
|
|
1204
1281
|
};
|
|
1282
|
+
const handlePaste = (content) => {
|
|
1283
|
+
const truncated = content.length > MAX_PASTE_SIZE ? content.slice(0, MAX_PASTE_SIZE) : content;
|
|
1284
|
+
const lineCount = truncated.split("\n").length;
|
|
1285
|
+
setPastedContent(truncated, lineCount);
|
|
1286
|
+
};
|
|
1205
1287
|
const handleChange = async (newValue) => {
|
|
1206
|
-
if (
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1288
|
+
if (pastedContent) {
|
|
1289
|
+
clearPaste();
|
|
1290
|
+
}
|
|
1291
|
+
if (newValue.length <= IMAGE_DETECTION_MAX_LENGTH) {
|
|
1292
|
+
if (ImageInputDetector.containsImageData(newValue)) {
|
|
1293
|
+
const imageEvent = ImageInputDetector.extractImageData(newValue);
|
|
1294
|
+
if (imageEvent && onImageDetected) {
|
|
1295
|
+
const placeholder = await onImageDetected(imageEvent.data);
|
|
1296
|
+
setValue(`${placeholder} `);
|
|
1297
|
+
return;
|
|
1298
|
+
}
|
|
1212
1299
|
}
|
|
1213
1300
|
}
|
|
1214
1301
|
setValue(newValue);
|
|
@@ -1239,6 +1326,8 @@ function InputPrompt({
|
|
|
1239
1326
|
value,
|
|
1240
1327
|
onChange: handleChange,
|
|
1241
1328
|
onSubmit: handleSubmit,
|
|
1329
|
+
onPaste: handlePaste,
|
|
1330
|
+
pasteIndicator: pastedContent ? `[pasted +${pastedLineCount} lines]` : null,
|
|
1242
1331
|
placeholder: getPlaceholder(),
|
|
1243
1332
|
showCursor: !disabled,
|
|
1244
1333
|
disabled
|
|
@@ -2762,9 +2851,327 @@ var CommandHistoryStore = class {
|
|
|
2762
2851
|
}
|
|
2763
2852
|
};
|
|
2764
2853
|
|
|
2765
|
-
// src/storage/
|
|
2766
|
-
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";
|
|
2767
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";
|
|
2768
3175
|
import os from "os";
|
|
2769
3176
|
|
|
2770
3177
|
// src/utils/commandParser.ts
|
|
@@ -2927,14 +3334,14 @@ var CustomCommandStore = class {
|
|
|
2927
3334
|
const home = os.homedir();
|
|
2928
3335
|
const root = projectRoot || process.cwd();
|
|
2929
3336
|
this.globalCommandsDirs = [
|
|
2930
|
-
|
|
2931
|
-
|
|
2932
|
-
|
|
3337
|
+
path7.join(home, ".bike4mind", "commands"),
|
|
3338
|
+
path7.join(home, ".claude", "commands"),
|
|
3339
|
+
path7.join(home, ".claude", "skills")
|
|
2933
3340
|
];
|
|
2934
3341
|
this.projectCommandsDirs = [
|
|
2935
|
-
|
|
2936
|
-
|
|
2937
|
-
|
|
3342
|
+
path7.join(root, ".bike4mind", "commands"),
|
|
3343
|
+
path7.join(root, ".claude", "commands"),
|
|
3344
|
+
path7.join(root, ".claude", "skills")
|
|
2938
3345
|
];
|
|
2939
3346
|
}
|
|
2940
3347
|
/**
|
|
@@ -2959,7 +3366,7 @@ var CustomCommandStore = class {
|
|
|
2959
3366
|
*/
|
|
2960
3367
|
async loadCommandsFromDirectory(directory, source) {
|
|
2961
3368
|
try {
|
|
2962
|
-
const stats = await
|
|
3369
|
+
const stats = await fs6.stat(directory);
|
|
2963
3370
|
if (!stats.isDirectory()) {
|
|
2964
3371
|
return;
|
|
2965
3372
|
}
|
|
@@ -2992,9 +3399,9 @@ var CustomCommandStore = class {
|
|
|
2992
3399
|
async findCommandFiles(directory) {
|
|
2993
3400
|
const files = [];
|
|
2994
3401
|
try {
|
|
2995
|
-
const entries = await
|
|
3402
|
+
const entries = await fs6.readdir(directory, { withFileTypes: true });
|
|
2996
3403
|
for (const entry of entries) {
|
|
2997
|
-
const fullPath =
|
|
3404
|
+
const fullPath = path7.join(directory, entry.name);
|
|
2998
3405
|
if (entry.isDirectory()) {
|
|
2999
3406
|
const subFiles = await this.findCommandFiles(fullPath);
|
|
3000
3407
|
files.push(...subFiles);
|
|
@@ -3014,14 +3421,14 @@ var CustomCommandStore = class {
|
|
|
3014
3421
|
* @param source - Source identifier ('global' or 'project')
|
|
3015
3422
|
*/
|
|
3016
3423
|
async loadCommandFile(filePath, source) {
|
|
3017
|
-
const filename =
|
|
3424
|
+
const filename = path7.basename(filePath);
|
|
3018
3425
|
const isSkillFile = filename.toLowerCase() === "skill.md";
|
|
3019
3426
|
const commandName = isSkillFile ? this.extractSkillName(filePath) : extractCommandName(filename);
|
|
3020
3427
|
if (!commandName) {
|
|
3021
3428
|
console.warn(`Invalid command filename: ${filename} (must end with .md and have valid name)`);
|
|
3022
3429
|
return;
|
|
3023
3430
|
}
|
|
3024
|
-
const fileContent = await
|
|
3431
|
+
const fileContent = await fs6.readFile(filePath, "utf-8");
|
|
3025
3432
|
const command = parseCommandFile(fileContent, filePath, commandName, source);
|
|
3026
3433
|
this.commands.set(commandName, command);
|
|
3027
3434
|
}
|
|
@@ -3033,7 +3440,7 @@ var CustomCommandStore = class {
|
|
|
3033
3440
|
* @returns Skill name or null if invalid
|
|
3034
3441
|
*/
|
|
3035
3442
|
extractSkillName(filePath) {
|
|
3036
|
-
const parentDir =
|
|
3443
|
+
const parentDir = path7.basename(path7.dirname(filePath));
|
|
3037
3444
|
return parentDir && parentDir !== "skills" ? parentDir : null;
|
|
3038
3445
|
}
|
|
3039
3446
|
/**
|
|
@@ -3095,15 +3502,15 @@ var CustomCommandStore = class {
|
|
|
3095
3502
|
*/
|
|
3096
3503
|
async createCommandFile(name, isGlobal = false) {
|
|
3097
3504
|
const targetDir = isGlobal ? this.globalCommandsDirs[0] : this.projectCommandsDirs[0];
|
|
3098
|
-
const filePath =
|
|
3099
|
-
const fileExists = await
|
|
3505
|
+
const filePath = path7.join(targetDir, `${name}.md`);
|
|
3506
|
+
const fileExists = await fs6.access(filePath).then(
|
|
3100
3507
|
() => true,
|
|
3101
3508
|
() => false
|
|
3102
3509
|
);
|
|
3103
3510
|
if (fileExists) {
|
|
3104
3511
|
throw new Error(`Command file already exists: ${filePath}`);
|
|
3105
3512
|
}
|
|
3106
|
-
await
|
|
3513
|
+
await fs6.mkdir(targetDir, { recursive: true });
|
|
3107
3514
|
const template = `---
|
|
3108
3515
|
description: ${name} command
|
|
3109
3516
|
argument-hint: [args]
|
|
@@ -3118,7 +3525,7 @@ You can use:
|
|
|
3118
3525
|
- $1, $2, etc. for positional arguments
|
|
3119
3526
|
- @filename for file references
|
|
3120
3527
|
`;
|
|
3121
|
-
await
|
|
3528
|
+
await fs6.writeFile(filePath, template, "utf-8");
|
|
3122
3529
|
return filePath;
|
|
3123
3530
|
}
|
|
3124
3531
|
};
|
|
@@ -4350,7 +4757,7 @@ import { GoogleGenerativeAI } from "@google/generative-ai";
|
|
|
4350
4757
|
import OpenAI2 from "openai";
|
|
4351
4758
|
|
|
4352
4759
|
// ../../b4m-core/packages/services/dist/src/importHistoryService/index.js
|
|
4353
|
-
import
|
|
4760
|
+
import fs7, { unlinkSync as unlinkSync2 } from "fs";
|
|
4354
4761
|
import yauzl from "yauzl";
|
|
4355
4762
|
import axios3 from "axios";
|
|
4356
4763
|
|
|
@@ -6041,8 +6448,8 @@ async function processAndStoreImages(images, context) {
|
|
|
6041
6448
|
const buffer = await downloadImage(image);
|
|
6042
6449
|
const fileType = await fileTypeFromBuffer2(buffer);
|
|
6043
6450
|
const filename = `${uuidv45()}.${fileType?.ext}`;
|
|
6044
|
-
const
|
|
6045
|
-
return
|
|
6451
|
+
const path21 = await context.imageGenerateStorage.upload(buffer, filename, {});
|
|
6452
|
+
return path21;
|
|
6046
6453
|
}));
|
|
6047
6454
|
}
|
|
6048
6455
|
async function updateQuestAndReturnMarkdown(storedImageUrls, context) {
|
|
@@ -7368,8 +7775,8 @@ async function processAndStoreImage(imageUrl, context) {
|
|
|
7368
7775
|
const buffer = await downloadImage2(imageUrl);
|
|
7369
7776
|
const fileType = await fileTypeFromBuffer3(buffer);
|
|
7370
7777
|
const filename = `${uuidv46()}.${fileType?.ext}`;
|
|
7371
|
-
const
|
|
7372
|
-
return
|
|
7778
|
+
const path21 = await context.imageGenerateStorage.upload(buffer, filename, {});
|
|
7779
|
+
return path21;
|
|
7373
7780
|
}
|
|
7374
7781
|
async function generateFullMask(imageBuffer) {
|
|
7375
7782
|
const sharp = (await import("sharp")).default;
|
|
@@ -9002,8 +9409,8 @@ var getHeliocentricCoords = (planet, T) => {
|
|
|
9002
9409
|
const sinNode = Math.sin(longNode);
|
|
9003
9410
|
const x = r * (cosNode * cosOmega - sinNode * sinOmega * cosI);
|
|
9004
9411
|
const y = r * (sinNode * cosOmega + cosNode * sinOmega * cosI);
|
|
9005
|
-
const
|
|
9006
|
-
return { x, y, z:
|
|
9412
|
+
const z148 = r * sinOmega * sinI;
|
|
9413
|
+
return { x, y, z: z148, r };
|
|
9007
9414
|
};
|
|
9008
9415
|
var getGeocentricEcliptic = (planet, earth) => {
|
|
9009
9416
|
const dx = planet.x - earth.x;
|
|
@@ -9310,33 +9717,33 @@ var planetVisibilityTool = {
|
|
|
9310
9717
|
};
|
|
9311
9718
|
|
|
9312
9719
|
// ../../b4m-core/packages/services/dist/src/llm/tools/implementation/fileRead/index.js
|
|
9313
|
-
import { promises as
|
|
9314
|
-
import { existsSync as
|
|
9315
|
-
import
|
|
9316
|
-
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;
|
|
9317
9724
|
async function readFileContent(params) {
|
|
9318
9725
|
const { path: filePath, encoding = "utf-8", offset = 0, limit } = params;
|
|
9319
|
-
const normalizedPath =
|
|
9320
|
-
const resolvedPath =
|
|
9321
|
-
const cwd =
|
|
9726
|
+
const normalizedPath = path8.normalize(filePath);
|
|
9727
|
+
const resolvedPath = path8.resolve(process.cwd(), normalizedPath);
|
|
9728
|
+
const cwd = path8.resolve(process.cwd());
|
|
9322
9729
|
if (!resolvedPath.startsWith(cwd)) {
|
|
9323
9730
|
throw new Error(`Access denied: Cannot read files outside of current working directory`);
|
|
9324
9731
|
}
|
|
9325
|
-
if (!
|
|
9732
|
+
if (!existsSync5(resolvedPath)) {
|
|
9326
9733
|
throw new Error(`File not found: ${filePath}`);
|
|
9327
9734
|
}
|
|
9328
9735
|
const stats = statSync4(resolvedPath);
|
|
9329
9736
|
if (stats.isDirectory()) {
|
|
9330
9737
|
throw new Error(`Path is a directory, not a file: ${filePath}`);
|
|
9331
9738
|
}
|
|
9332
|
-
if (stats.size >
|
|
9333
|
-
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)`);
|
|
9334
9741
|
}
|
|
9335
9742
|
const isBinary = await checkIfBinary(resolvedPath);
|
|
9336
9743
|
if (isBinary && encoding === "utf-8") {
|
|
9337
9744
|
throw new Error(`File appears to be binary. Use encoding 'base64' to read binary files, or specify a different encoding.`);
|
|
9338
9745
|
}
|
|
9339
|
-
const content = await
|
|
9746
|
+
const content = await fs8.readFile(resolvedPath, encoding);
|
|
9340
9747
|
if (typeof content === "string") {
|
|
9341
9748
|
const lines = content.split("\n");
|
|
9342
9749
|
const totalLines = lines.length;
|
|
@@ -9374,7 +9781,7 @@ ${content}`;
|
|
|
9374
9781
|
}
|
|
9375
9782
|
async function checkIfBinary(filePath) {
|
|
9376
9783
|
const buffer = Buffer.alloc(8192);
|
|
9377
|
-
const fd = await
|
|
9784
|
+
const fd = await fs8.open(filePath, "r");
|
|
9378
9785
|
try {
|
|
9379
9786
|
const { bytesRead } = await fd.read(buffer, 0, 8192, 0);
|
|
9380
9787
|
const chunk = buffer.slice(0, bytesRead);
|
|
@@ -9391,7 +9798,7 @@ var fileReadTool = {
|
|
|
9391
9798
|
context.logger.info("\u{1F4C4} FileRead: Reading file", { path: params.path });
|
|
9392
9799
|
try {
|
|
9393
9800
|
const content = await readFileContent(params);
|
|
9394
|
-
const stats = statSync4(
|
|
9801
|
+
const stats = statSync4(path8.resolve(process.cwd(), path8.normalize(params.path)));
|
|
9395
9802
|
context.logger.info("\u2705 FileRead: Success", {
|
|
9396
9803
|
path: params.path,
|
|
9397
9804
|
size: stats.size,
|
|
@@ -9435,25 +9842,25 @@ var fileReadTool = {
|
|
|
9435
9842
|
};
|
|
9436
9843
|
|
|
9437
9844
|
// ../../b4m-core/packages/services/dist/src/llm/tools/implementation/createFile/index.js
|
|
9438
|
-
import { promises as
|
|
9439
|
-
import { existsSync as
|
|
9440
|
-
import
|
|
9845
|
+
import { promises as fs9 } from "fs";
|
|
9846
|
+
import { existsSync as existsSync6 } from "fs";
|
|
9847
|
+
import path9 from "path";
|
|
9441
9848
|
async function createFile(params) {
|
|
9442
9849
|
const { path: filePath, content, createDirectories = true } = params;
|
|
9443
|
-
const normalizedPath =
|
|
9444
|
-
const resolvedPath =
|
|
9445
|
-
const cwd =
|
|
9850
|
+
const normalizedPath = path9.normalize(filePath);
|
|
9851
|
+
const resolvedPath = path9.resolve(process.cwd(), normalizedPath);
|
|
9852
|
+
const cwd = path9.resolve(process.cwd());
|
|
9446
9853
|
if (!resolvedPath.startsWith(cwd)) {
|
|
9447
9854
|
throw new Error(`Access denied: Cannot create files outside of current working directory`);
|
|
9448
9855
|
}
|
|
9449
|
-
const fileExists =
|
|
9856
|
+
const fileExists = existsSync6(resolvedPath);
|
|
9450
9857
|
const action = fileExists ? "overwritten" : "created";
|
|
9451
9858
|
if (createDirectories) {
|
|
9452
|
-
const dir =
|
|
9453
|
-
await
|
|
9859
|
+
const dir = path9.dirname(resolvedPath);
|
|
9860
|
+
await fs9.mkdir(dir, { recursive: true });
|
|
9454
9861
|
}
|
|
9455
|
-
await
|
|
9456
|
-
const stats = await
|
|
9862
|
+
await fs9.writeFile(resolvedPath, content, "utf-8");
|
|
9863
|
+
const stats = await fs9.stat(resolvedPath);
|
|
9457
9864
|
const lines = content.split("\n").length;
|
|
9458
9865
|
return `File ${action} successfully: ${filePath}
|
|
9459
9866
|
Size: ${stats.size} bytes
|
|
@@ -9464,7 +9871,7 @@ var createFileTool = {
|
|
|
9464
9871
|
implementation: (context) => ({
|
|
9465
9872
|
toolFn: async (value) => {
|
|
9466
9873
|
const params = value;
|
|
9467
|
-
const fileExists =
|
|
9874
|
+
const fileExists = existsSync6(path9.resolve(process.cwd(), path9.normalize(params.path)));
|
|
9468
9875
|
context.logger.info(`\u{1F4DD} CreateFile: ${fileExists ? "Overwriting" : "Creating"} file`, {
|
|
9469
9876
|
path: params.path,
|
|
9470
9877
|
size: params.content.length
|
|
@@ -9506,7 +9913,7 @@ var createFileTool = {
|
|
|
9506
9913
|
// ../../b4m-core/packages/services/dist/src/llm/tools/implementation/globFiles/index.js
|
|
9507
9914
|
import { glob } from "glob";
|
|
9508
9915
|
import { stat } from "fs/promises";
|
|
9509
|
-
import
|
|
9916
|
+
import path10 from "path";
|
|
9510
9917
|
var DEFAULT_IGNORE_PATTERNS = [
|
|
9511
9918
|
"**/node_modules/**",
|
|
9512
9919
|
"**/.git/**",
|
|
@@ -9522,7 +9929,7 @@ var DEFAULT_IGNORE_PATTERNS = [
|
|
|
9522
9929
|
async function findFiles(params) {
|
|
9523
9930
|
const { pattern, dir_path, case_sensitive = true, respect_git_ignore = true } = params;
|
|
9524
9931
|
const baseCwd = process.cwd();
|
|
9525
|
-
const targetDir = dir_path ?
|
|
9932
|
+
const targetDir = dir_path ? path10.resolve(baseCwd, path10.normalize(dir_path)) : baseCwd;
|
|
9526
9933
|
if (!targetDir.startsWith(baseCwd)) {
|
|
9527
9934
|
throw new Error(`Access denied: Cannot search outside of current working directory`);
|
|
9528
9935
|
}
|
|
@@ -9562,7 +9969,7 @@ async function findFiles(params) {
|
|
|
9562
9969
|
const summary = `Found ${filesWithStats.length} file(s)${truncated ? ` (showing first ${MAX_RESULTS})` : ""} matching: ${pattern}`;
|
|
9563
9970
|
const dirInfo = dir_path ? `
|
|
9564
9971
|
Directory: ${dir_path}` : "";
|
|
9565
|
-
const filesList = results.map((file) =>
|
|
9972
|
+
const filesList = results.map((file) => path10.relative(baseCwd, file.path)).join("\n");
|
|
9566
9973
|
return `${summary}${dirInfo}
|
|
9567
9974
|
|
|
9568
9975
|
${filesList}`;
|
|
@@ -9616,27 +10023,48 @@ var globFilesTool = {
|
|
|
9616
10023
|
|
|
9617
10024
|
// ../../b4m-core/packages/services/dist/src/llm/tools/implementation/grepSearch/index.js
|
|
9618
10025
|
import { stat as stat2 } from "fs/promises";
|
|
9619
|
-
import
|
|
9620
|
-
import
|
|
10026
|
+
import { existsSync as existsSync7 } from "fs";
|
|
10027
|
+
import path11 from "path";
|
|
10028
|
+
import { execFile, execFileSync as execFileSync2 } from "child_process";
|
|
9621
10029
|
import { promisify } from "util";
|
|
9622
10030
|
import { createRequire } from "module";
|
|
9623
10031
|
var execFileAsync = promisify(execFile);
|
|
9624
10032
|
var require2 = createRequire(import.meta.url);
|
|
10033
|
+
var cachedRgPath = null;
|
|
9625
10034
|
function getRipgrepPath() {
|
|
10035
|
+
if (cachedRgPath)
|
|
10036
|
+
return cachedRgPath;
|
|
9626
10037
|
try {
|
|
9627
10038
|
const ripgrepPath = require2.resolve("@vscode/ripgrep");
|
|
9628
|
-
const ripgrepDir =
|
|
9629
|
-
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;
|
|
9630
10055
|
return rgBinary;
|
|
9631
10056
|
} catch (error) {
|
|
10057
|
+
if (error instanceof Error && error.message.includes("ripgrep binary not found")) {
|
|
10058
|
+
throw error;
|
|
10059
|
+
}
|
|
9632
10060
|
throw new Error("ripgrep is not available. Please install @vscode/ripgrep: pnpm add @vscode/ripgrep --filter @bike4mind/services");
|
|
9633
10061
|
}
|
|
9634
10062
|
}
|
|
9635
10063
|
function isPathWithinWorkspace(targetPath, baseCwd) {
|
|
9636
|
-
const resolvedTarget =
|
|
9637
|
-
const resolvedBase =
|
|
9638
|
-
const relativePath =
|
|
9639
|
-
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);
|
|
9640
10068
|
}
|
|
9641
10069
|
function convertGlobToRipgrepGlobs(globPattern) {
|
|
9642
10070
|
if (!globPattern || globPattern === "**/*") {
|
|
@@ -9652,7 +10080,7 @@ function convertGlobToRipgrepGlobs(globPattern) {
|
|
|
9652
10080
|
async function searchFiles2(params) {
|
|
9653
10081
|
const { pattern, dir_path, include } = params;
|
|
9654
10082
|
const baseCwd = process.cwd();
|
|
9655
|
-
const targetDir = dir_path ?
|
|
10083
|
+
const targetDir = dir_path ? path11.resolve(baseCwd, dir_path) : baseCwd;
|
|
9656
10084
|
if (!isPathWithinWorkspace(targetDir, baseCwd)) {
|
|
9657
10085
|
throw new Error(`Path validation failed: "${dir_path}" resolves outside the allowed workspace directory`);
|
|
9658
10086
|
}
|
|
@@ -9714,7 +10142,7 @@ async function searchFiles2(params) {
|
|
|
9714
10142
|
if (item.type === "match") {
|
|
9715
10143
|
const match = item;
|
|
9716
10144
|
allMatches.push({
|
|
9717
|
-
filePath:
|
|
10145
|
+
filePath: path11.relative(targetDir, match.data.path.text) || path11.basename(match.data.path.text),
|
|
9718
10146
|
lineNumber: match.data.line_number,
|
|
9719
10147
|
line: match.data.lines.text.trimEnd()
|
|
9720
10148
|
// Remove trailing newline
|
|
@@ -9807,18 +10235,18 @@ var grepSearchTool = {
|
|
|
9807
10235
|
};
|
|
9808
10236
|
|
|
9809
10237
|
// ../../b4m-core/packages/services/dist/src/llm/tools/implementation/deleteFile/index.js
|
|
9810
|
-
import { promises as
|
|
9811
|
-
import { existsSync as
|
|
9812
|
-
import
|
|
10238
|
+
import { promises as fs10 } from "fs";
|
|
10239
|
+
import { existsSync as existsSync8, statSync as statSync5 } from "fs";
|
|
10240
|
+
import path12 from "path";
|
|
9813
10241
|
async function deleteFile(params) {
|
|
9814
10242
|
const { path: filePath, recursive = false } = params;
|
|
9815
|
-
const normalizedPath =
|
|
9816
|
-
const resolvedPath =
|
|
9817
|
-
const cwd =
|
|
10243
|
+
const normalizedPath = path12.normalize(filePath);
|
|
10244
|
+
const resolvedPath = path12.resolve(process.cwd(), normalizedPath);
|
|
10245
|
+
const cwd = path12.resolve(process.cwd());
|
|
9818
10246
|
if (!resolvedPath.startsWith(cwd)) {
|
|
9819
10247
|
throw new Error(`Access denied: Cannot delete files outside of current working directory`);
|
|
9820
10248
|
}
|
|
9821
|
-
if (!
|
|
10249
|
+
if (!existsSync8(resolvedPath)) {
|
|
9822
10250
|
throw new Error(`File or directory not found: ${filePath}`);
|
|
9823
10251
|
}
|
|
9824
10252
|
const stats = statSync5(resolvedPath);
|
|
@@ -9828,10 +10256,10 @@ async function deleteFile(params) {
|
|
|
9828
10256
|
throw new Error(`Path is a directory: ${filePath}. Use recursive=true to delete directories and their contents.`);
|
|
9829
10257
|
}
|
|
9830
10258
|
if (isDirectory) {
|
|
9831
|
-
await
|
|
10259
|
+
await fs10.rm(resolvedPath, { recursive: true, force: true });
|
|
9832
10260
|
return `Directory deleted successfully: ${filePath}`;
|
|
9833
10261
|
} else {
|
|
9834
|
-
await
|
|
10262
|
+
await fs10.unlink(resolvedPath);
|
|
9835
10263
|
return `File deleted successfully: ${filePath}
|
|
9836
10264
|
Size: ${size} bytes`;
|
|
9837
10265
|
}
|
|
@@ -9841,8 +10269,8 @@ var deleteFileTool = {
|
|
|
9841
10269
|
implementation: (context) => ({
|
|
9842
10270
|
toolFn: async (value) => {
|
|
9843
10271
|
const params = value;
|
|
9844
|
-
const resolvedPath =
|
|
9845
|
-
const isDirectory =
|
|
10272
|
+
const resolvedPath = path12.resolve(process.cwd(), path12.normalize(params.path));
|
|
10273
|
+
const isDirectory = existsSync8(resolvedPath) && statSync5(resolvedPath).isDirectory();
|
|
9846
10274
|
context.logger.info(`\u{1F5D1}\uFE0F DeleteFile: Deleting ${isDirectory ? "directory" : "file"}`, {
|
|
9847
10275
|
path: params.path,
|
|
9848
10276
|
recursive: params.recursive
|
|
@@ -9887,13 +10315,13 @@ function formatSearchResults(files) {
|
|
|
9887
10315
|
const notes = file.notes ? `
|
|
9888
10316
|
Notes: ${file.notes}` : "";
|
|
9889
10317
|
const fileType = file.type || "FILE";
|
|
9890
|
-
return `${index + 1}. **${file.fileName}**
|
|
10318
|
+
return `${index + 1}. **${file.fileName}** (ID: ${file.id})
|
|
9891
10319
|
Type: ${fileType} | MIME: ${file.mimeType}
|
|
9892
10320
|
Tags: ${tags}${notes}`;
|
|
9893
10321
|
});
|
|
9894
10322
|
return `Found ${files.length} document(s) in your knowledge base:
|
|
9895
10323
|
|
|
9896
|
-
` + 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.*";
|
|
9897
10325
|
}
|
|
9898
10326
|
var knowledgeBaseSearchTool = {
|
|
9899
10327
|
name: "search_knowledge_base",
|
|
@@ -9920,6 +10348,8 @@ var knowledgeBaseSearchTool = {
|
|
|
9920
10348
|
by: "fileName",
|
|
9921
10349
|
direction: "asc"
|
|
9922
10350
|
}, {
|
|
10351
|
+
textSearch: true,
|
|
10352
|
+
// Search across fileName + tags + notes for better recall
|
|
9923
10353
|
includeShared: true,
|
|
9924
10354
|
// Include owned + explicitly shared + org-shared files
|
|
9925
10355
|
userGroups: context.user.groups || []
|
|
@@ -9934,18 +10364,18 @@ var knowledgeBaseSearchTool = {
|
|
|
9934
10364
|
},
|
|
9935
10365
|
toolSchema: {
|
|
9936
10366
|
name: "search_knowledge_base",
|
|
9937
|
-
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.",
|
|
9938
10368
|
parameters: {
|
|
9939
10369
|
type: "object",
|
|
9940
10370
|
properties: {
|
|
9941
10371
|
query: {
|
|
9942
10372
|
type: "string",
|
|
9943
|
-
description: "The search query to find relevant documents
|
|
10373
|
+
description: "The search query to find relevant documents. Matches against file names, tags, and notes."
|
|
9944
10374
|
},
|
|
9945
10375
|
tags: {
|
|
9946
10376
|
type: "array",
|
|
9947
10377
|
items: { type: "string" },
|
|
9948
|
-
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.'
|
|
9949
10379
|
},
|
|
9950
10380
|
file_type: {
|
|
9951
10381
|
type: "string",
|
|
@@ -9965,64 +10395,1686 @@ var knowledgeBaseSearchTool = {
|
|
|
9965
10395
|
})
|
|
9966
10396
|
};
|
|
9967
10397
|
|
|
9968
|
-
// ../../b4m-core/packages/services/dist/src/llm/tools/implementation/
|
|
9969
|
-
|
|
9970
|
-
|
|
9971
|
-
var
|
|
9972
|
-
|
|
9973
|
-
|
|
9974
|
-
|
|
9975
|
-
|
|
9976
|
-
|
|
9977
|
-
|
|
9978
|
-
|
|
9979
|
-
|
|
9980
|
-
|
|
9981
|
-
|
|
9982
|
-
|
|
9983
|
-
|
|
9984
|
-
|
|
9985
|
-
|
|
9986
|
-
|
|
9987
|
-
|
|
9988
|
-
|
|
9989
|
-
|
|
9990
|
-
|
|
9991
|
-
|
|
9992
|
-
|
|
9993
|
-
|
|
9994
|
-
|
|
9995
|
-
|
|
9996
|
-
|
|
9997
|
-
|
|
9998
|
-
|
|
9999
|
-
|
|
10000
|
-
|
|
10001
|
-
|
|
10002
|
-
|
|
10003
|
-
|
|
10004
|
-
|
|
10005
|
-
|
|
10006
|
-
|
|
10007
|
-
|
|
10008
|
-
|
|
10009
|
-
|
|
10010
|
-
|
|
10011
|
-
|
|
10012
|
-
|
|
10013
|
-
|
|
10014
|
-
|
|
10015
|
-
|
|
10016
|
-
|
|
10017
|
-
|
|
10018
|
-
|
|
10019
|
-
|
|
10020
|
-
|
|
10021
|
-
|
|
10022
|
-
|
|
10023
|
-
|
|
10024
|
-
|
|
10025
|
-
|
|
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 },
|
|
10026
12078
|
{ pattern: /\bkillall\s+-9\b/i, reason: "Force killing processes", block: false },
|
|
10027
12079
|
{ pattern: /\bshutdown\b/i, reason: "System shutdown", block: true },
|
|
10028
12080
|
{ pattern: /\breboot\b/i, reason: "System reboot", block: true },
|
|
@@ -10151,7 +12203,7 @@ async function executeBashCommand(params) {
|
|
|
10151
12203
|
};
|
|
10152
12204
|
}
|
|
10153
12205
|
const baseCwd = process.cwd();
|
|
10154
|
-
const targetCwd = relativeCwd ?
|
|
12206
|
+
const targetCwd = relativeCwd ? path13.resolve(baseCwd, relativeCwd) : baseCwd;
|
|
10155
12207
|
const effectiveTimeout = Math.min(timeout, 5 * 60 * 1e3);
|
|
10156
12208
|
return new Promise((resolve3) => {
|
|
10157
12209
|
let stdout = "";
|
|
@@ -10214,7 +12266,7 @@ async function executeBashCommand(params) {
|
|
|
10214
12266
|
});
|
|
10215
12267
|
});
|
|
10216
12268
|
}
|
|
10217
|
-
function
|
|
12269
|
+
function formatResult2(result, command) {
|
|
10218
12270
|
const parts = [];
|
|
10219
12271
|
parts.push(`$ ${command}`);
|
|
10220
12272
|
parts.push("");
|
|
@@ -10268,7 +12320,7 @@ var bashExecuteTool = {
|
|
|
10268
12320
|
}
|
|
10269
12321
|
try {
|
|
10270
12322
|
const result = await executeBashCommand(params);
|
|
10271
|
-
const formattedResult =
|
|
12323
|
+
const formattedResult = formatResult2(result, params.command);
|
|
10272
12324
|
context.logger.info("Bash: Command completed", {
|
|
10273
12325
|
exitCode: result.exitCode,
|
|
10274
12326
|
timedOut: result.timedOut,
|
|
@@ -11154,9 +13206,9 @@ function parseQuery(query) {
|
|
|
11154
13206
|
}
|
|
11155
13207
|
|
|
11156
13208
|
// ../../b4m-core/packages/services/dist/src/llm/tools/implementation/editLocalFile/index.js
|
|
11157
|
-
import { promises as
|
|
11158
|
-
import { existsSync as
|
|
11159
|
-
import
|
|
13209
|
+
import { promises as fs11 } from "fs";
|
|
13210
|
+
import { existsSync as existsSync9 } from "fs";
|
|
13211
|
+
import path14 from "path";
|
|
11160
13212
|
import { diffLines as diffLines3 } from "diff";
|
|
11161
13213
|
function generateDiff(original, modified) {
|
|
11162
13214
|
const differences = diffLines3(original, modified);
|
|
@@ -11180,16 +13232,16 @@ function generateDiff(original, modified) {
|
|
|
11180
13232
|
}
|
|
11181
13233
|
async function editLocalFile(params) {
|
|
11182
13234
|
const { path: filePath, old_string, new_string } = params;
|
|
11183
|
-
const normalizedPath =
|
|
11184
|
-
const resolvedPath =
|
|
11185
|
-
const cwd =
|
|
13235
|
+
const normalizedPath = path14.normalize(filePath);
|
|
13236
|
+
const resolvedPath = path14.resolve(process.cwd(), normalizedPath);
|
|
13237
|
+
const cwd = path14.resolve(process.cwd());
|
|
11186
13238
|
if (!resolvedPath.startsWith(cwd)) {
|
|
11187
13239
|
throw new Error(`Access denied: Cannot edit files outside of current working directory`);
|
|
11188
13240
|
}
|
|
11189
|
-
if (!
|
|
13241
|
+
if (!existsSync9(resolvedPath)) {
|
|
11190
13242
|
throw new Error(`File not found: ${filePath}`);
|
|
11191
13243
|
}
|
|
11192
|
-
const currentContent = await
|
|
13244
|
+
const currentContent = await fs11.readFile(resolvedPath, "utf-8");
|
|
11193
13245
|
if (!currentContent.includes(old_string)) {
|
|
11194
13246
|
const preview = old_string.length > 100 ? old_string.substring(0, 100) + "..." : old_string;
|
|
11195
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}"`);
|
|
@@ -11199,7 +13251,7 @@ async function editLocalFile(params) {
|
|
|
11199
13251
|
throw new Error(`Found ${occurrences} occurrences of the string to replace. Please provide a more specific old_string that matches exactly one location.`);
|
|
11200
13252
|
}
|
|
11201
13253
|
const newContent = currentContent.replace(old_string, new_string);
|
|
11202
|
-
await
|
|
13254
|
+
await fs11.writeFile(resolvedPath, newContent, "utf-8");
|
|
11203
13255
|
const diffResult = generateDiff(old_string, new_string);
|
|
11204
13256
|
return `File edited successfully: ${filePath}
|
|
11205
13257
|
Changes: +${diffResult.additions} lines, -${diffResult.deletions} lines
|
|
@@ -11437,7 +13489,7 @@ async function getFileStats(files, since, filterPath) {
|
|
|
11437
13489
|
});
|
|
11438
13490
|
});
|
|
11439
13491
|
}
|
|
11440
|
-
function
|
|
13492
|
+
function formatResult3(result) {
|
|
11441
13493
|
const parts = [];
|
|
11442
13494
|
if (result.error) {
|
|
11443
13495
|
parts.push(`Error: ${result.error}`);
|
|
@@ -11512,7 +13564,7 @@ var recentChangesTool = {
|
|
|
11512
13564
|
}
|
|
11513
13565
|
try {
|
|
11514
13566
|
const result = await getRecentChanges(params);
|
|
11515
|
-
const formattedResult =
|
|
13567
|
+
const formattedResult = formatResult3(result);
|
|
11516
13568
|
context.logger.info("RecentChanges: Retrieved file changes", {
|
|
11517
13569
|
totalFiles: result.totalFiles,
|
|
11518
13570
|
displayedFiles: result.files.length,
|
|
@@ -11610,8 +13662,14 @@ var b4mTools = {
|
|
|
11610
13662
|
sunrise_sunset: sunriseSunsetTool,
|
|
11611
13663
|
iss_tracker: issTrackerTool,
|
|
11612
13664
|
planet_visibility: planetVisibilityTool,
|
|
11613
|
-
// Knowledge base
|
|
11614
|
-
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
|
|
11615
13673
|
};
|
|
11616
13674
|
var cliOnlyTools = {
|
|
11617
13675
|
// File operation tools
|
|
@@ -11653,6 +13711,20 @@ var generateTools = (userId, user, logger2, { db }, storage, imageGenerateStorag
|
|
|
11653
13711
|
[key]: tool.implementation(context, config[key])
|
|
11654
13712
|
}), {});
|
|
11655
13713
|
};
|
|
13714
|
+
function normalizeToolParameters(rest) {
|
|
13715
|
+
const rawParameters = rest?.input_schema ?? rest?.inputSchema ?? rest?.parameters;
|
|
13716
|
+
if (rawParameters && typeof rawParameters === "object") {
|
|
13717
|
+
return {
|
|
13718
|
+
...rawParameters,
|
|
13719
|
+
properties: rawParameters.properties ?? {}
|
|
13720
|
+
};
|
|
13721
|
+
}
|
|
13722
|
+
return {
|
|
13723
|
+
type: "object",
|
|
13724
|
+
properties: {},
|
|
13725
|
+
additionalProperties: true
|
|
13726
|
+
};
|
|
13727
|
+
}
|
|
11656
13728
|
var ATLASSIAN_RECONNECT_MESSAGE = "\u26A0\uFE0F Your Atlassian connection has expired.\n\nPlease reconnect your Atlassian account in Settings > Connected Apps to continue using Confluence and Jira tools.";
|
|
11657
13729
|
var generateMcpTools = async (mcpData) => {
|
|
11658
13730
|
let tools;
|
|
@@ -11679,12 +13751,7 @@ var generateMcpTools = async (mcpData) => {
|
|
|
11679
13751
|
const namespacedToolName = `${serverName}__${originalToolName}`;
|
|
11680
13752
|
const providerMetadata = getMcpProviderMetadata(serverName);
|
|
11681
13753
|
const fallbackDescription = providerMetadata?.defaultToolDescriptions?.[originalToolName] ?? "";
|
|
11682
|
-
const
|
|
11683
|
-
const parameters = rawParameters && typeof rawParameters === "object" ? rawParameters : {
|
|
11684
|
-
type: "object",
|
|
11685
|
-
properties: {},
|
|
11686
|
-
additionalProperties: true
|
|
11687
|
-
};
|
|
13754
|
+
const parameters = normalizeToolParameters(rest);
|
|
11688
13755
|
const optionTools = {
|
|
11689
13756
|
toolFn: async (args) => {
|
|
11690
13757
|
Logger.debug(`Calling ${originalToolName} tool via ${mcpData.serverName}`, args);
|
|
@@ -11737,94 +13804,8 @@ var generateMcpTools = async (mcpData) => {
|
|
|
11737
13804
|
return result;
|
|
11738
13805
|
};
|
|
11739
13806
|
|
|
11740
|
-
// ../../b4m-core/packages/services/dist/src/llm/agents/
|
|
11741
|
-
var
|
|
11742
|
-
name: "explore",
|
|
11743
|
-
description: "Fast research, exploration, and information search",
|
|
11744
|
-
model: config?.model ?? "claude-3-5-haiku-20241022",
|
|
11745
|
-
defaultThoroughness: config?.defaultThoroughness ?? "medium",
|
|
11746
|
-
maxIterations: { quick: 2, medium: 5, very_thorough: 10 },
|
|
11747
|
-
deniedTools: [
|
|
11748
|
-
"image_generation",
|
|
11749
|
-
"edit_image",
|
|
11750
|
-
"deep_research",
|
|
11751
|
-
"delegate_to_agent",
|
|
11752
|
-
...config?.extraDeniedTools ?? []
|
|
11753
|
-
],
|
|
11754
|
-
allowedTools: config?.extraAllowedTools,
|
|
11755
|
-
systemPrompt: `You are a research and exploration specialist. Your job is to search and analyze information efficiently.
|
|
11756
|
-
|
|
11757
|
-
## Focus Areas
|
|
11758
|
-
- Finding relevant information across knowledge bases and the web
|
|
11759
|
-
- Understanding context and patterns
|
|
11760
|
-
- Providing clear, concise summaries
|
|
11761
|
-
|
|
11762
|
-
## Tool Usage
|
|
11763
|
-
Use these tools strategically:
|
|
11764
|
-
- \`web_search\` - Search the web for information
|
|
11765
|
-
- \`web_fetch\` - Fetch and read full webpage content
|
|
11766
|
-
- \`search_knowledge_base\` - Search the user's knowledge bases
|
|
11767
|
-
- \`current_datetime\` - Get the current date and time
|
|
11768
|
-
|
|
11769
|
-
## Search Strategy
|
|
11770
|
-
1. Start with targeted searches using specific keywords
|
|
11771
|
-
2. Use web_fetch to read full content from promising results
|
|
11772
|
-
3. Cross-reference findings from multiple sources
|
|
11773
|
-
4. Check knowledge bases for relevant internal information
|
|
11774
|
-
|
|
11775
|
-
## Output Format
|
|
11776
|
-
Provide a clear summary including:
|
|
11777
|
-
1. What you found (key facts, sources, patterns)
|
|
11778
|
-
2. Key insights or observations
|
|
11779
|
-
3. Relevant source references
|
|
11780
|
-
|
|
11781
|
-
Be thorough but concise. Your summary will be used by the main agent.`
|
|
11782
|
-
});
|
|
11783
|
-
|
|
11784
|
-
// ../../b4m-core/packages/services/dist/src/llm/agents/PlanAgent.js
|
|
11785
|
-
var PlanAgent = (config) => ({
|
|
11786
|
-
name: "plan",
|
|
11787
|
-
description: "Task breakdown and implementation planning",
|
|
11788
|
-
model: config?.model ?? "claude-3-5-haiku-20241022",
|
|
11789
|
-
defaultThoroughness: config?.defaultThoroughness ?? "medium",
|
|
11790
|
-
maxIterations: { quick: 3, medium: 7, very_thorough: 12 },
|
|
11791
|
-
deniedTools: [
|
|
11792
|
-
"image_generation",
|
|
11793
|
-
"edit_image",
|
|
11794
|
-
"deep_research",
|
|
11795
|
-
"delegate_to_agent",
|
|
11796
|
-
...config?.extraDeniedTools ?? []
|
|
11797
|
-
],
|
|
11798
|
-
allowedTools: config?.extraAllowedTools,
|
|
11799
|
-
systemPrompt: `You are a task planning specialist. Your job is to break down complex tasks into clear, actionable steps.
|
|
11800
|
-
|
|
11801
|
-
## Focus Areas
|
|
11802
|
-
- Identifying dependencies and blockers
|
|
11803
|
-
- Creating logical sequence of steps
|
|
11804
|
-
- Researching context before planning
|
|
11805
|
-
|
|
11806
|
-
## Process
|
|
11807
|
-
1. First, research the topic to understand the current landscape
|
|
11808
|
-
2. Identify what already exists vs. what needs to be done
|
|
11809
|
-
3. Break down work into discrete, actionable steps
|
|
11810
|
-
4. Order steps by dependencies
|
|
11811
|
-
|
|
11812
|
-
## Output Format
|
|
11813
|
-
Provide a structured plan:
|
|
11814
|
-
|
|
11815
|
-
### Prerequisites
|
|
11816
|
-
- What must exist before starting
|
|
11817
|
-
|
|
11818
|
-
### Steps
|
|
11819
|
-
1. Step with clear deliverable
|
|
11820
|
-
2. Next step (depends on: Step 1)
|
|
11821
|
-
...
|
|
11822
|
-
|
|
11823
|
-
### Risks & Considerations
|
|
11824
|
-
- Potential issues to watch for
|
|
11825
|
-
|
|
11826
|
-
Be specific and actionable. Your plan will be used by the main agent.`
|
|
11827
|
-
});
|
|
13807
|
+
// ../../b4m-core/packages/services/dist/src/llm/agents/ServerSubagentOrchestrator.js
|
|
13808
|
+
var SUBAGENT_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
11828
13809
|
|
|
11829
13810
|
// ../../b4m-core/packages/services/dist/src/llm/agents/CodeReviewAgent.js
|
|
11830
13811
|
var CodeReviewAgent = (config) => ({
|
|
@@ -11919,12 +13900,27 @@ var GithubManagerAgent = (config) => ({
|
|
|
11919
13900
|
exclusiveMcpServers: ["github"],
|
|
11920
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.
|
|
11921
13902
|
|
|
11922
|
-
## First Step: Resolve Repository Context
|
|
11923
|
-
|
|
13903
|
+
## First Step: ALWAYS Resolve Repository Context Automatically
|
|
13904
|
+
Before doing ANYTHING else, call the \`github__current_user\` tool. The response includes:
|
|
11924
13905
|
- **selected_repositories**: The list of repositories the user has enabled for AI access (in \`owner/repo\` format)
|
|
11925
13906
|
- **user**: The authenticated user's profile (login, name, etc.)
|
|
11926
13907
|
|
|
11927
|
-
|
|
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.
|
|
11928
13924
|
|
|
11929
13925
|
## Capabilities
|
|
11930
13926
|
|
|
@@ -11965,7 +13961,7 @@ Use this to resolve the owner and repo name when the user does not specify them.
|
|
|
11965
13961
|
## Output Format
|
|
11966
13962
|
Provide a clear summary of actions taken:
|
|
11967
13963
|
1. What was done (created, searched, merged, etc.)
|
|
11968
|
-
2.
|
|
13964
|
+
2. Always provide links to relevant items (e.g., owner/repo#123, PR URLs)
|
|
11969
13965
|
3. Any issues or warnings encountered
|
|
11970
13966
|
|
|
11971
13967
|
Be precise with repository names, issue numbers, and PR numbers. Your results will be used by the main agent.`
|
|
@@ -11975,8 +13971,9 @@ Be precise with repository names, issue numbers, and PR numbers. Your results wi
|
|
|
11975
13971
|
var ServerAgentStore = class {
|
|
11976
13972
|
constructor() {
|
|
11977
13973
|
const builtInAgents = [
|
|
11978
|
-
|
|
11979
|
-
|
|
13974
|
+
// For now disable explore and plan agents as it is much faster if the parent llm handles this.
|
|
13975
|
+
// ExploreAgent(),
|
|
13976
|
+
// PlanAgent(),
|
|
11980
13977
|
CodeReviewAgent(),
|
|
11981
13978
|
ProjectManagerAgent(),
|
|
11982
13979
|
GithubManagerAgent()
|
|
@@ -12017,50 +14014,48 @@ var serverAgentStore = new ServerAgentStore();
|
|
|
12017
14014
|
import throttle2 from "lodash/throttle.js";
|
|
12018
14015
|
|
|
12019
14016
|
// ../../b4m-core/packages/services/dist/src/llm/ChatCompletionFeatures.js
|
|
12020
|
-
import { z as
|
|
14017
|
+
import { z as z140 } from "zod";
|
|
12021
14018
|
import uniq4 from "lodash/uniq.js";
|
|
12022
|
-
var QuestStartBodySchema =
|
|
12023
|
-
userId:
|
|
12024
|
-
sessionId:
|
|
12025
|
-
questId:
|
|
12026
|
-
message:
|
|
12027
|
-
messageFileIds:
|
|
12028
|
-
historyCount:
|
|
12029
|
-
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()),
|
|
12030
14027
|
params: ChatCompletionCreateInputSchema,
|
|
12031
14028
|
dashboardParams: DashboardParamsSchema.optional(),
|
|
12032
|
-
enableQuestMaster:
|
|
12033
|
-
enableMementos:
|
|
12034
|
-
enableArtifacts:
|
|
12035
|
-
enableAgents:
|
|
12036
|
-
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(),
|
|
12037
14034
|
promptMeta: PromptMetaZodSchema,
|
|
12038
|
-
tools:
|
|
12039
|
-
mcpServers:
|
|
12040
|
-
projectId:
|
|
12041
|
-
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(),
|
|
12042
14039
|
questMaster: QuestMasterParamsSchema.optional(),
|
|
12043
|
-
toolPromptId:
|
|
14040
|
+
toolPromptId: z140.string().optional(),
|
|
12044
14041
|
researchMode: ResearchModeParamsSchema.optional(),
|
|
12045
|
-
fallbackModel:
|
|
12046
|
-
embeddingModel:
|
|
12047
|
-
queryComplexity:
|
|
14042
|
+
fallbackModel: z140.string().optional(),
|
|
14043
|
+
embeddingModel: z140.string().optional(),
|
|
14044
|
+
queryComplexity: z140.string(),
|
|
12048
14045
|
imageConfig: GenerateImageToolCallSchema.optional(),
|
|
12049
|
-
deepResearchConfig:
|
|
12050
|
-
maxDepth:
|
|
12051
|
-
duration:
|
|
14046
|
+
deepResearchConfig: z140.object({
|
|
14047
|
+
maxDepth: z140.number().optional(),
|
|
14048
|
+
duration: z140.number().optional(),
|
|
12052
14049
|
// Note: searchers are passed via ToolContext and not through this API schema
|
|
12053
|
-
searchers:
|
|
14050
|
+
searchers: z140.array(z140.any()).optional()
|
|
12054
14051
|
}).optional(),
|
|
12055
|
-
extraContextMessages:
|
|
12056
|
-
role:
|
|
12057
|
-
content:
|
|
12058
|
-
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()
|
|
12059
14056
|
})).optional(),
|
|
12060
14057
|
/** User's timezone (IANA format, e.g., "America/New_York") */
|
|
12061
|
-
timezone:
|
|
12062
|
-
/** Pre-fetched API key table from invoke phase — avoids redundant DB call in process (#6616 P1-a) */
|
|
12063
|
-
apiKeyTable: z139.record(z139.string(), z139.string().nullable()).optional()
|
|
14058
|
+
timezone: z140.string().optional()
|
|
12064
14059
|
});
|
|
12065
14060
|
|
|
12066
14061
|
// ../../b4m-core/packages/services/dist/src/llm/StatusManager.js
|
|
@@ -12560,89 +14555,89 @@ var BUILT_IN_TOOL_SET = new Set(b4mLLMTools.options);
|
|
|
12560
14555
|
import axios8 from "axios";
|
|
12561
14556
|
import { fileTypeFromBuffer as fileTypeFromBuffer4 } from "file-type";
|
|
12562
14557
|
import { v4 as uuidv47 } from "uuid";
|
|
12563
|
-
import { z as
|
|
14558
|
+
import { z as z141 } from "zod";
|
|
12564
14559
|
import { fromZodError as fromZodError2 } from "zod-validation-error";
|
|
12565
14560
|
var ImageGenerationBodySchema = OpenAIImageGenerationInput.extend({
|
|
12566
|
-
sessionId:
|
|
12567
|
-
questId:
|
|
12568
|
-
userId:
|
|
12569
|
-
prompt:
|
|
12570
|
-
organizationId:
|
|
12571
|
-
safety_tolerance:
|
|
12572
|
-
prompt_upsampling:
|
|
12573
|
-
seed:
|
|
12574
|
-
output_format:
|
|
12575
|
-
width:
|
|
12576
|
-
height:
|
|
12577
|
-
aspect_ratio:
|
|
12578
|
-
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()
|
|
12579
14574
|
});
|
|
12580
14575
|
|
|
12581
14576
|
// ../../b4m-core/packages/services/dist/src/llm/VideoGeneration.js
|
|
12582
14577
|
import axios9 from "axios";
|
|
12583
14578
|
import { v4 as uuidv48 } from "uuid";
|
|
12584
|
-
import { z as
|
|
14579
|
+
import { z as z142 } from "zod";
|
|
12585
14580
|
import { fromZodError as fromZodError3 } from "zod-validation-error";
|
|
12586
|
-
var VideoGenerationBodySchema =
|
|
12587
|
-
sessionId:
|
|
12588
|
-
questId:
|
|
12589
|
-
userId:
|
|
12590
|
-
prompt:
|
|
12591
|
-
model:
|
|
12592
|
-
seconds:
|
|
12593
|
-
size:
|
|
12594
|
-
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()
|
|
12595
14590
|
});
|
|
12596
14591
|
|
|
12597
14592
|
// ../../b4m-core/packages/services/dist/src/llm/ImageEdit.js
|
|
12598
14593
|
import axios10 from "axios";
|
|
12599
14594
|
import { fileTypeFromBuffer as fileTypeFromBuffer5 } from "file-type";
|
|
12600
14595
|
import { v4 as uuidv49 } from "uuid";
|
|
12601
|
-
import { z as
|
|
14596
|
+
import { z as z143 } from "zod";
|
|
12602
14597
|
import { fromZodError as fromZodError4 } from "zod-validation-error";
|
|
12603
14598
|
var ImageEditBodySchema = OpenAIImageGenerationInput.extend({
|
|
12604
|
-
sessionId:
|
|
12605
|
-
questId:
|
|
12606
|
-
userId:
|
|
12607
|
-
prompt:
|
|
12608
|
-
organizationId:
|
|
12609
|
-
safety_tolerance:
|
|
12610
|
-
prompt_upsampling:
|
|
12611
|
-
seed:
|
|
12612
|
-
output_format:
|
|
12613
|
-
width:
|
|
12614
|
-
height:
|
|
12615
|
-
aspect_ratio:
|
|
12616
|
-
size:
|
|
12617
|
-
fabFileIds:
|
|
12618
|
-
image:
|
|
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()
|
|
12619
14614
|
});
|
|
12620
14615
|
|
|
12621
14616
|
// ../../b4m-core/packages/services/dist/src/llm/refineText.js
|
|
12622
|
-
import { z as
|
|
12623
|
-
var refineTextLLMSchema =
|
|
12624
|
-
text:
|
|
12625
|
-
context:
|
|
14617
|
+
import { z as z144 } from "zod";
|
|
14618
|
+
var refineTextLLMSchema = z144.object({
|
|
14619
|
+
text: z144.string(),
|
|
14620
|
+
context: z144.string().optional()
|
|
12626
14621
|
// tone: z.enum(['formal', 'informal', 'neutral']).optional(),
|
|
12627
14622
|
// style: z.enum(['technical', 'creative', 'academic', 'business', 'narrative']).optional(),
|
|
12628
14623
|
});
|
|
12629
14624
|
|
|
12630
14625
|
// ../../b4m-core/packages/services/dist/src/llm/MementoEvaluationService.js
|
|
12631
|
-
import { z as
|
|
12632
|
-
var SingleMementoEvalSchema =
|
|
12633
|
-
importance:
|
|
14626
|
+
import { z as z145 } from "zod";
|
|
14627
|
+
var SingleMementoEvalSchema = z145.object({
|
|
14628
|
+
importance: z145.number().min(1).max(10),
|
|
12634
14629
|
// 1-10 scale for personal info importance
|
|
12635
|
-
summary:
|
|
12636
|
-
tags:
|
|
14630
|
+
summary: z145.string(),
|
|
14631
|
+
tags: z145.array(z145.string()).optional()
|
|
12637
14632
|
});
|
|
12638
|
-
var MementoEvalResponseSchema =
|
|
12639
|
-
isPersonal:
|
|
12640
|
-
mementos:
|
|
14633
|
+
var MementoEvalResponseSchema = z145.object({
|
|
14634
|
+
isPersonal: z145.boolean(),
|
|
14635
|
+
mementos: z145.array(SingleMementoEvalSchema).optional()
|
|
12641
14636
|
// Array of distinct personal information
|
|
12642
14637
|
});
|
|
12643
14638
|
|
|
12644
14639
|
// ../../b4m-core/packages/services/dist/src/llm/SmallLLMService.js
|
|
12645
|
-
import { z as
|
|
14640
|
+
import { z as z146 } from "zod";
|
|
12646
14641
|
|
|
12647
14642
|
// ../../b4m-core/packages/services/dist/src/auth/AccessTokenGeneratorService.js
|
|
12648
14643
|
import jwt2 from "jsonwebtoken";
|
|
@@ -12661,10 +14656,10 @@ var ToolErrorType;
|
|
|
12661
14656
|
// src/utils/diffPreview.ts
|
|
12662
14657
|
import * as Diff from "diff";
|
|
12663
14658
|
import { readFile } from "fs/promises";
|
|
12664
|
-
import { existsSync as
|
|
14659
|
+
import { existsSync as existsSync10 } from "fs";
|
|
12665
14660
|
async function generateFileDiffPreview(args) {
|
|
12666
14661
|
try {
|
|
12667
|
-
if (!
|
|
14662
|
+
if (!existsSync10(args.path)) {
|
|
12668
14663
|
const lines2 = args.content.split("\n");
|
|
12669
14664
|
const preview = lines2.slice(0, 20).join("\n");
|
|
12670
14665
|
const hasMore = lines2.length > 20;
|
|
@@ -12702,10 +14697,10 @@ ${diffLines4.join("\n")}`;
|
|
|
12702
14697
|
}
|
|
12703
14698
|
async function generateFileDeletePreview(args) {
|
|
12704
14699
|
try {
|
|
12705
|
-
if (!
|
|
14700
|
+
if (!existsSync10(args.path)) {
|
|
12706
14701
|
return `[File does not exist: ${args.path}]`;
|
|
12707
14702
|
}
|
|
12708
|
-
const stats = await import("fs/promises").then((
|
|
14703
|
+
const stats = await import("fs/promises").then((fs15) => fs15.stat(args.path));
|
|
12709
14704
|
return `[File will be deleted]
|
|
12710
14705
|
|
|
12711
14706
|
Path: ${args.path}
|
|
@@ -12758,6 +14753,10 @@ var ServerToolExecutor = class {
|
|
|
12758
14753
|
};
|
|
12759
14754
|
|
|
12760
14755
|
// src/llm/ToolRouter.ts
|
|
14756
|
+
var wsToolExecutor = null;
|
|
14757
|
+
function setWebSocketToolExecutor(executor) {
|
|
14758
|
+
wsToolExecutor = executor;
|
|
14759
|
+
}
|
|
12761
14760
|
var SERVER_TOOLS = ["weather_info", "web_search", "web_fetch"];
|
|
12762
14761
|
var LOCAL_TOOLS = [
|
|
12763
14762
|
"file_read",
|
|
@@ -12780,7 +14779,15 @@ function isLocalTool(toolName) {
|
|
|
12780
14779
|
}
|
|
12781
14780
|
async function executeTool(toolName, input, apiClient, localToolFn) {
|
|
12782
14781
|
if (isServerTool(toolName)) {
|
|
12783
|
-
|
|
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`);
|
|
12784
14791
|
const executor = new ServerToolExecutor(apiClient);
|
|
12785
14792
|
return await executor.executeTool(toolName, input);
|
|
12786
14793
|
} else if (isLocalTool(toolName)) {
|
|
@@ -12935,7 +14942,7 @@ function buildHookContext(params) {
|
|
|
12935
14942
|
}
|
|
12936
14943
|
|
|
12937
14944
|
// src/agents/types.ts
|
|
12938
|
-
import { z as
|
|
14945
|
+
import { z as z147 } from "zod";
|
|
12939
14946
|
var HookBlockedError = class extends Error {
|
|
12940
14947
|
constructor(toolName, reason) {
|
|
12941
14948
|
super(`Hook blocked execution of ${toolName}: ${reason || "No reason provided"}`);
|
|
@@ -12947,40 +14954,40 @@ var ALWAYS_DENIED_FOR_AGENTS = [
|
|
|
12947
14954
|
"agent_delegate"
|
|
12948
14955
|
// No agent chaining
|
|
12949
14956
|
];
|
|
12950
|
-
var CommandHookSchema =
|
|
12951
|
-
type:
|
|
12952
|
-
command:
|
|
12953
|
-
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()
|
|
12954
14961
|
});
|
|
12955
|
-
var PromptHookSchema =
|
|
12956
|
-
type:
|
|
12957
|
-
prompt:
|
|
12958
|
-
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()
|
|
12959
14966
|
});
|
|
12960
|
-
var HookDefinitionSchema =
|
|
12961
|
-
var HookMatcherSchema =
|
|
12962
|
-
matcher:
|
|
12963
|
-
hooks:
|
|
14967
|
+
var HookDefinitionSchema = z147.discriminatedUnion("type", [CommandHookSchema, PromptHookSchema]);
|
|
14968
|
+
var HookMatcherSchema = z147.object({
|
|
14969
|
+
matcher: z147.string().optional(),
|
|
14970
|
+
hooks: z147.array(HookDefinitionSchema)
|
|
12964
14971
|
});
|
|
12965
|
-
var AgentHooksSchema =
|
|
12966
|
-
PreToolUse:
|
|
12967
|
-
PostToolUse:
|
|
12968
|
-
PostToolUseFailure:
|
|
12969
|
-
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()
|
|
12970
14977
|
}).optional();
|
|
12971
|
-
var AgentFrontmatterSchema =
|
|
12972
|
-
description:
|
|
12973
|
-
model:
|
|
12974
|
-
"allowed-tools":
|
|
12975
|
-
"denied-tools":
|
|
12976
|
-
skills:
|
|
12977
|
-
"max-iterations":
|
|
12978
|
-
quick:
|
|
12979
|
-
medium:
|
|
12980
|
-
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()
|
|
12981
14988
|
}).optional(),
|
|
12982
|
-
"default-thoroughness":
|
|
12983
|
-
variables:
|
|
14989
|
+
"default-thoroughness": z147.enum(["quick", "medium", "very_thorough"]).optional(),
|
|
14990
|
+
variables: z147.record(z147.string()).optional(),
|
|
12984
14991
|
hooks: AgentHooksSchema
|
|
12985
14992
|
});
|
|
12986
14993
|
var DEFAULT_MAX_ITERATIONS = {
|
|
@@ -12992,25 +14999,26 @@ var DEFAULT_AGENT_MODEL = "claude-3-5-haiku-20241022";
|
|
|
12992
14999
|
var DEFAULT_THOROUGHNESS = "medium";
|
|
12993
15000
|
|
|
12994
15001
|
// src/utils/toolsAdapter.ts
|
|
15002
|
+
import path15 from "path";
|
|
12995
15003
|
var NoOpStorage = class extends BaseStorage {
|
|
12996
15004
|
async upload(input, destination, options) {
|
|
12997
15005
|
return `/tmp/${destination}`;
|
|
12998
15006
|
}
|
|
12999
|
-
async download(
|
|
15007
|
+
async download(path21) {
|
|
13000
15008
|
throw new Error("Download not supported in CLI");
|
|
13001
15009
|
}
|
|
13002
|
-
async delete(
|
|
15010
|
+
async delete(path21) {
|
|
13003
15011
|
}
|
|
13004
|
-
async getSignedUrl(
|
|
13005
|
-
return `/tmp/${
|
|
15012
|
+
async getSignedUrl(path21) {
|
|
15013
|
+
return `/tmp/${path21}`;
|
|
13006
15014
|
}
|
|
13007
|
-
getPublicUrl(
|
|
13008
|
-
return `/tmp/${
|
|
15015
|
+
getPublicUrl(path21) {
|
|
15016
|
+
return `/tmp/${path21}`;
|
|
13009
15017
|
}
|
|
13010
|
-
async getPreview(
|
|
13011
|
-
return `/tmp/${
|
|
15018
|
+
async getPreview(path21) {
|
|
15019
|
+
return `/tmp/${path21}`;
|
|
13012
15020
|
}
|
|
13013
|
-
async getMetadata(
|
|
15021
|
+
async getMetadata(path21) {
|
|
13014
15022
|
return { size: 0, contentType: "application/octet-stream" };
|
|
13015
15023
|
}
|
|
13016
15024
|
};
|
|
@@ -13041,7 +15049,7 @@ function wrapToolWithPermission(tool, permissionManager, showPermissionPrompt, a
|
|
|
13041
15049
|
agentContext.observationQueue.push({ toolName, result: result2 });
|
|
13042
15050
|
return result2;
|
|
13043
15051
|
}
|
|
13044
|
-
const { useCliStore: useCliStore2 } = await import("./store-
|
|
15052
|
+
const { useCliStore: useCliStore2 } = await import("./store-K5MB3SE7.js");
|
|
13045
15053
|
if (useCliStore2.getState().autoAcceptEdits) {
|
|
13046
15054
|
const result2 = await executeTool(toolName, args, apiClient, originalFn);
|
|
13047
15055
|
agentContext.observationQueue.push({ toolName, result: result2 });
|
|
@@ -13183,6 +15191,27 @@ function wrapToolWithHooks(tool, hooks, hookContext) {
|
|
|
13183
15191
|
}
|
|
13184
15192
|
};
|
|
13185
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
|
+
}
|
|
13186
15215
|
var TOOL_NAME_MAPPING = {
|
|
13187
15216
|
// Claude Code -> B4M
|
|
13188
15217
|
read: "file_read",
|
|
@@ -13207,7 +15236,7 @@ function normalizeToolName(toolName) {
|
|
|
13207
15236
|
}
|
|
13208
15237
|
return TOOL_NAME_MAPPING[toolName] || toolName;
|
|
13209
15238
|
}
|
|
13210
|
-
function generateCliTools(userId, llm, model, permissionManager, showPermissionPrompt, agentContext, configStore, apiClient, toolFilter) {
|
|
15239
|
+
function generateCliTools(userId, llm, model, permissionManager, showPermissionPrompt, agentContext, configStore, apiClient, toolFilter, checkpointStore) {
|
|
13211
15240
|
const logger2 = new CliLogger();
|
|
13212
15241
|
const storage = new NoOpStorage();
|
|
13213
15242
|
const user = {
|
|
@@ -13266,9 +15295,17 @@ function generateCliTools(userId, llm, model, permissionManager, showPermissionP
|
|
|
13266
15295
|
// imageProcessorLambdaName (not needed for CLI)
|
|
13267
15296
|
tools_to_generate
|
|
13268
15297
|
);
|
|
13269
|
-
let tools = Object.entries(toolsMap).map(
|
|
13270
|
-
|
|
13271
|
-
|
|
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
|
+
});
|
|
13272
15309
|
if (toolFilter) {
|
|
13273
15310
|
const { allowedTools, deniedTools } = toolFilter;
|
|
13274
15311
|
const normalizedAllowed = allowedTools?.map(normalizeToolName);
|
|
@@ -13446,8 +15483,8 @@ function getEnvironmentName(configApiConfig) {
|
|
|
13446
15483
|
}
|
|
13447
15484
|
|
|
13448
15485
|
// src/utils/contextLoader.ts
|
|
13449
|
-
import * as
|
|
13450
|
-
import * as
|
|
15486
|
+
import * as fs12 from "fs";
|
|
15487
|
+
import * as path16 from "path";
|
|
13451
15488
|
import { homedir as homedir3 } from "os";
|
|
13452
15489
|
var CONTEXT_FILE_SIZE_LIMIT = 100 * 1024;
|
|
13453
15490
|
var PROJECT_CONTEXT_FILES = [
|
|
@@ -13468,9 +15505,9 @@ function formatFileSize2(bytes) {
|
|
|
13468
15505
|
return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
|
|
13469
15506
|
}
|
|
13470
15507
|
function tryReadContextFile(dir, filename, source) {
|
|
13471
|
-
const filePath =
|
|
15508
|
+
const filePath = path16.join(dir, filename);
|
|
13472
15509
|
try {
|
|
13473
|
-
const stats =
|
|
15510
|
+
const stats = fs12.lstatSync(filePath);
|
|
13474
15511
|
if (stats.isDirectory()) {
|
|
13475
15512
|
return null;
|
|
13476
15513
|
}
|
|
@@ -13484,7 +15521,7 @@ function tryReadContextFile(dir, filename, source) {
|
|
|
13484
15521
|
error: `${source === "global" ? "Global" : "Project"} ${filename} exceeds 100KB limit (${formatFileSize2(stats.size)})`
|
|
13485
15522
|
};
|
|
13486
15523
|
}
|
|
13487
|
-
const content =
|
|
15524
|
+
const content = fs12.readFileSync(filePath, "utf-8");
|
|
13488
15525
|
return {
|
|
13489
15526
|
filename,
|
|
13490
15527
|
content,
|
|
@@ -13536,7 +15573,7 @@ ${project.content}`;
|
|
|
13536
15573
|
}
|
|
13537
15574
|
async function loadContextFiles(projectDir) {
|
|
13538
15575
|
const errors = [];
|
|
13539
|
-
const globalDir =
|
|
15576
|
+
const globalDir = path16.join(homedir3(), ".bike4mind");
|
|
13540
15577
|
const projectDirectory = projectDir || process.cwd();
|
|
13541
15578
|
const [globalResult, projectResult] = await Promise.all([
|
|
13542
15579
|
Promise.resolve(findContextFile(globalDir, GLOBAL_CONTEXT_FILES, "global")),
|
|
@@ -13807,8 +15844,8 @@ function substituteArguments(template, args) {
|
|
|
13807
15844
|
// ../../b4m-core/packages/mcp/dist/src/client.js
|
|
13808
15845
|
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
13809
15846
|
import { Client as Client2 } from "@modelcontextprotocol/sdk/client/index.js";
|
|
13810
|
-
import
|
|
13811
|
-
import { existsSync as
|
|
15847
|
+
import path17 from "path";
|
|
15848
|
+
import { existsSync as existsSync11, readdirSync as readdirSync3 } from "fs";
|
|
13812
15849
|
var MCPClient = class {
|
|
13813
15850
|
// Note: This class handles MCP server communication with repository filtering
|
|
13814
15851
|
mcp;
|
|
@@ -13849,18 +15886,18 @@ var MCPClient = class {
|
|
|
13849
15886
|
const root = process.env.INIT_CWD || process.cwd();
|
|
13850
15887
|
const candidatePaths = [
|
|
13851
15888
|
// When running from SST Lambda with node_modules structure (copyFiles)
|
|
13852
|
-
|
|
15889
|
+
path17.join(root, `node_modules/@bike4mind/mcp/dist/src/${this.serverName}/index.js`),
|
|
13853
15890
|
// When running from SST Lambda deployed environment (/var/task)
|
|
13854
|
-
|
|
15891
|
+
path17.join(root, `b4m-core/packages/mcp/dist/src/${this.serverName}/index.js`),
|
|
13855
15892
|
// When running from SST Lambda (.sst/artifacts/mcpHandler-dev), navigate to monorepo root (3 levels up)
|
|
13856
|
-
|
|
15893
|
+
path17.join(root, `../../../b4m-core/packages/mcp/dist/src/${this.serverName}/index.js`),
|
|
13857
15894
|
// When running from packages/client (Next.js app), navigate to monorepo root (2 levels up)
|
|
13858
|
-
|
|
15895
|
+
path17.join(root, `../../b4m-core/packages/mcp/dist/src/${this.serverName}/index.js`),
|
|
13859
15896
|
// Original paths (backward compatibility)
|
|
13860
|
-
|
|
13861
|
-
|
|
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")
|
|
13862
15899
|
];
|
|
13863
|
-
const serverScriptPath = candidatePaths.find((p) =>
|
|
15900
|
+
const serverScriptPath = candidatePaths.find((p) => existsSync11(p));
|
|
13864
15901
|
if (!serverScriptPath) {
|
|
13865
15902
|
const getDirectories = (source) => readdirSync3(source, { withFileTypes: true }).filter((dirent) => dirent.isDirectory()).map((dirent) => dirent.name);
|
|
13866
15903
|
console.error(`[MCP] Server script not found. Tried paths:`, candidatePaths);
|
|
@@ -14742,6 +16779,173 @@ var ServerLlmBackend = class {
|
|
|
14742
16779
|
}
|
|
14743
16780
|
};
|
|
14744
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
|
+
|
|
14745
16949
|
// src/llm/NotifyingLlmBackend.ts
|
|
14746
16950
|
var NotifyingLlmBackend = class {
|
|
14747
16951
|
constructor(inner, backgroundManager) {
|
|
@@ -14776,6 +16980,253 @@ Please acknowledge these background agent results and incorporate them into your
|
|
|
14776
16980
|
}
|
|
14777
16981
|
};
|
|
14778
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
|
+
|
|
14779
17230
|
// src/auth/ApiClient.ts
|
|
14780
17231
|
import axios11 from "axios";
|
|
14781
17232
|
var ApiClient = class {
|
|
@@ -14913,7 +17364,7 @@ import { isAxiosError as isAxiosError2 } from "axios";
|
|
|
14913
17364
|
// package.json
|
|
14914
17365
|
var package_default = {
|
|
14915
17366
|
name: "@bike4mind/cli",
|
|
14916
|
-
version: "0.2.
|
|
17367
|
+
version: "0.2.31-b4m-cli-undo-command.19493+44c80c9bc",
|
|
14917
17368
|
type: "module",
|
|
14918
17369
|
description: "Interactive CLI tool for Bike4Mind with ReAct agents",
|
|
14919
17370
|
license: "UNLICENSED",
|
|
@@ -14984,13 +17435,10 @@ var package_default = {
|
|
|
14984
17435
|
diff: "^8.0.2",
|
|
14985
17436
|
dotenv: "^16.3.1",
|
|
14986
17437
|
"eventsource-parser": "^3.0.6",
|
|
14987
|
-
fdir: "^6.5.0",
|
|
14988
17438
|
"file-type": "^18.7.0",
|
|
14989
17439
|
"fuse.js": "^7.1.0",
|
|
14990
|
-
fzf: "^0.5.2",
|
|
14991
17440
|
glob: "^13.0.0",
|
|
14992
17441
|
"gray-matter": "^4.0.3",
|
|
14993
|
-
ignore: "^7.0.5",
|
|
14994
17442
|
ink: "^6.5.1",
|
|
14995
17443
|
"ink-select-input": "^6.2.0",
|
|
14996
17444
|
"ink-spinner": "^5.0.0",
|
|
@@ -15027,10 +17475,10 @@ var package_default = {
|
|
|
15027
17475
|
},
|
|
15028
17476
|
devDependencies: {
|
|
15029
17477
|
"@bike4mind/agents": "0.1.0",
|
|
15030
|
-
"@bike4mind/common": "2.
|
|
15031
|
-
"@bike4mind/mcp": "1.
|
|
15032
|
-
"@bike4mind/services": "2.
|
|
15033
|
-
"@bike4mind/utils": "2.
|
|
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",
|
|
15034
17482
|
"@types/better-sqlite3": "^7.6.13",
|
|
15035
17483
|
"@types/diff": "^5.0.9",
|
|
15036
17484
|
"@types/jsonwebtoken": "^9.0.4",
|
|
@@ -15039,6 +17487,9 @@ var package_default = {
|
|
|
15039
17487
|
"@types/react": "^19.2.7",
|
|
15040
17488
|
"@types/uuid": "^9.0.7",
|
|
15041
17489
|
"@types/yargs": "^17.0.32",
|
|
17490
|
+
fdir: "^6.5.0",
|
|
17491
|
+
fzf: "^0.5.2",
|
|
17492
|
+
ignore: "^7.0.5",
|
|
15042
17493
|
"ink-testing-library": "^4.0.0",
|
|
15043
17494
|
tsup: "^8.5.1",
|
|
15044
17495
|
tsx: "^4.21.0",
|
|
@@ -15048,14 +17499,9 @@ var package_default = {
|
|
|
15048
17499
|
optionalDependencies: {
|
|
15049
17500
|
"@vscode/ripgrep": "^1.17.0"
|
|
15050
17501
|
},
|
|
15051
|
-
gitHead: "
|
|
17502
|
+
gitHead: "44c80c9bcf8bf132a8b75c3635426a901adce99e"
|
|
15052
17503
|
};
|
|
15053
17504
|
|
|
15054
|
-
// src/config/constants.ts
|
|
15055
|
-
var USAGE_DAYS = 30;
|
|
15056
|
-
var MODEL_NAME_COLUMN_WIDTH = 18;
|
|
15057
|
-
var USAGE_CACHE_TTL = 5 * 60 * 1e3;
|
|
15058
|
-
|
|
15059
17505
|
// src/agents/toolFilter.ts
|
|
15060
17506
|
function matchesToolPattern3(toolName, pattern) {
|
|
15061
17507
|
const regexPattern = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*");
|
|
@@ -15344,7 +17790,10 @@ var SubagentOrchestrator = class {
|
|
|
15344
17790
|
this.deps.showPermissionPrompt,
|
|
15345
17791
|
agentContext,
|
|
15346
17792
|
this.deps.configStore,
|
|
15347
|
-
this.deps.apiClient
|
|
17793
|
+
this.deps.apiClient,
|
|
17794
|
+
void 0,
|
|
17795
|
+
// toolFilter (applied separately below)
|
|
17796
|
+
this.deps.checkpointStore
|
|
15348
17797
|
);
|
|
15349
17798
|
const filteredTools = filterToolsByPatterns2(allTools, toolFilter.allowedTools, toolFilter.deniedTools);
|
|
15350
17799
|
if (this.deps.customCommandStore) {
|
|
@@ -15497,8 +17946,8 @@ var SubagentOrchestrator = class {
|
|
|
15497
17946
|
};
|
|
15498
17947
|
|
|
15499
17948
|
// src/agents/AgentStore.ts
|
|
15500
|
-
import
|
|
15501
|
-
import
|
|
17949
|
+
import fs13 from "fs/promises";
|
|
17950
|
+
import path18 from "path";
|
|
15502
17951
|
import os2 from "os";
|
|
15503
17952
|
import matter2 from "gray-matter";
|
|
15504
17953
|
var FULL_MODEL_ID_PREFIXES = [
|
|
@@ -15647,10 +18096,10 @@ var AgentStore = class {
|
|
|
15647
18096
|
const root = projectRoot || process.cwd();
|
|
15648
18097
|
const home = os2.homedir();
|
|
15649
18098
|
this.builtinAgentsDir = builtinDir;
|
|
15650
|
-
this.globalB4MAgentsDir =
|
|
15651
|
-
this.globalClaudeAgentsDir =
|
|
15652
|
-
this.projectB4MAgentsDir =
|
|
15653
|
-
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");
|
|
15654
18103
|
}
|
|
15655
18104
|
/**
|
|
15656
18105
|
* Load all agents from all directories
|
|
@@ -15674,7 +18123,7 @@ var AgentStore = class {
|
|
|
15674
18123
|
*/
|
|
15675
18124
|
async loadAgentsFromDirectory(directory, source) {
|
|
15676
18125
|
try {
|
|
15677
|
-
const stats = await
|
|
18126
|
+
const stats = await fs13.stat(directory);
|
|
15678
18127
|
if (!stats.isDirectory()) {
|
|
15679
18128
|
return;
|
|
15680
18129
|
}
|
|
@@ -15702,9 +18151,9 @@ var AgentStore = class {
|
|
|
15702
18151
|
async findAgentFiles(directory) {
|
|
15703
18152
|
const files = [];
|
|
15704
18153
|
try {
|
|
15705
|
-
const entries = await
|
|
18154
|
+
const entries = await fs13.readdir(directory, { withFileTypes: true });
|
|
15706
18155
|
for (const entry of entries) {
|
|
15707
|
-
const fullPath =
|
|
18156
|
+
const fullPath = path18.join(directory, entry.name);
|
|
15708
18157
|
if (entry.isDirectory()) {
|
|
15709
18158
|
const subFiles = await this.findAgentFiles(fullPath);
|
|
15710
18159
|
files.push(...subFiles);
|
|
@@ -15721,10 +18170,10 @@ var AgentStore = class {
|
|
|
15721
18170
|
* Parse a single agent markdown file
|
|
15722
18171
|
*/
|
|
15723
18172
|
async parseAgentFile(filePath, source) {
|
|
15724
|
-
const content = await
|
|
18173
|
+
const content = await fs13.readFile(filePath, "utf-8");
|
|
15725
18174
|
const { data: frontmatter, content: body } = matter2(content);
|
|
15726
18175
|
const parsed = AgentFrontmatterSchema.parse(frontmatter);
|
|
15727
|
-
const name =
|
|
18176
|
+
const name = path18.basename(filePath, ".md");
|
|
15728
18177
|
const modelInput = parsed.model || DEFAULT_AGENT_MODEL;
|
|
15729
18178
|
const resolution = resolveModelAlias(modelInput, name, filePath);
|
|
15730
18179
|
if (!resolution.resolved && resolution.warning) {
|
|
@@ -15804,16 +18253,16 @@ var AgentStore = class {
|
|
|
15804
18253
|
*/
|
|
15805
18254
|
async createAgentFile(name, isGlobal = false, useClaude = true) {
|
|
15806
18255
|
const targetDir = isGlobal ? useClaude ? this.globalClaudeAgentsDir : this.globalB4MAgentsDir : useClaude ? this.projectClaudeAgentsDir : this.projectB4MAgentsDir;
|
|
15807
|
-
const filePath =
|
|
18256
|
+
const filePath = path18.join(targetDir, `${name}.md`);
|
|
15808
18257
|
try {
|
|
15809
|
-
await
|
|
18258
|
+
await fs13.access(filePath);
|
|
15810
18259
|
throw new Error(`Agent file already exists: ${filePath}`);
|
|
15811
18260
|
} catch (error) {
|
|
15812
18261
|
if (error.code !== "ENOENT") {
|
|
15813
18262
|
throw error;
|
|
15814
18263
|
}
|
|
15815
18264
|
}
|
|
15816
|
-
await
|
|
18265
|
+
await fs13.mkdir(targetDir, { recursive: true });
|
|
15817
18266
|
const template = `---
|
|
15818
18267
|
description: ${name} agent description
|
|
15819
18268
|
model: claude-3-5-haiku-20241022
|
|
@@ -15848,7 +18297,7 @@ You are a ${name} specialist. Your job is to [describe primary task].
|
|
|
15848
18297
|
## Output Format
|
|
15849
18298
|
Describe the expected output format here.
|
|
15850
18299
|
`;
|
|
15851
|
-
await
|
|
18300
|
+
await fs13.writeFile(filePath, template, "utf-8");
|
|
15852
18301
|
return filePath;
|
|
15853
18302
|
}
|
|
15854
18303
|
/**
|
|
@@ -16501,7 +18950,7 @@ function createTodoStore(onUpdate) {
|
|
|
16501
18950
|
|
|
16502
18951
|
// src/tools/findDefinitionTool.ts
|
|
16503
18952
|
import { stat as stat3 } from "fs/promises";
|
|
16504
|
-
import
|
|
18953
|
+
import path19 from "path";
|
|
16505
18954
|
import { execFile as execFile2 } from "child_process";
|
|
16506
18955
|
import { promisify as promisify2 } from "util";
|
|
16507
18956
|
import { createRequire as createRequire2 } from "module";
|
|
@@ -16522,8 +18971,8 @@ var ALL_KEYWORDS = Object.values(KIND_KEYWORDS).flat();
|
|
|
16522
18971
|
function getRipgrepPath2() {
|
|
16523
18972
|
try {
|
|
16524
18973
|
const ripgrepPath = require3.resolve("@vscode/ripgrep");
|
|
16525
|
-
const ripgrepDir =
|
|
16526
|
-
return
|
|
18974
|
+
const ripgrepDir = path19.dirname(ripgrepPath);
|
|
18975
|
+
return path19.join(ripgrepDir, "..", "bin", "rg" + (process.platform === "win32" ? ".exe" : ""));
|
|
16527
18976
|
} catch {
|
|
16528
18977
|
throw new Error(
|
|
16529
18978
|
"ripgrep is not available. Please install @vscode/ripgrep: pnpm add @vscode/ripgrep --filter @bike4mind/services"
|
|
@@ -16531,10 +18980,10 @@ function getRipgrepPath2() {
|
|
|
16531
18980
|
}
|
|
16532
18981
|
}
|
|
16533
18982
|
function isPathWithinWorkspace2(targetPath, baseCwd) {
|
|
16534
|
-
const resolvedTarget =
|
|
16535
|
-
const resolvedBase =
|
|
16536
|
-
const relativePath =
|
|
16537
|
-
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);
|
|
16538
18987
|
}
|
|
16539
18988
|
function escapeRegex(str) {
|
|
16540
18989
|
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
@@ -16567,7 +19016,7 @@ async function findDefinitions(params) {
|
|
|
16567
19016
|
throw new Error("symbol_name is required");
|
|
16568
19017
|
}
|
|
16569
19018
|
const baseCwd = process.cwd();
|
|
16570
|
-
const targetDir = search_path ?
|
|
19019
|
+
const targetDir = search_path ? path19.resolve(baseCwd, search_path) : baseCwd;
|
|
16571
19020
|
if (!isPathWithinWorkspace2(targetDir, baseCwd)) {
|
|
16572
19021
|
throw new Error(`Path validation failed: "${search_path}" resolves outside the allowed workspace directory`);
|
|
16573
19022
|
}
|
|
@@ -16622,7 +19071,7 @@ async function findDefinitions(params) {
|
|
|
16622
19071
|
const lineText = match.data.lines.text.trimEnd();
|
|
16623
19072
|
if (isLikelyDefinition(lineText)) {
|
|
16624
19073
|
allMatches.push({
|
|
16625
|
-
filePath:
|
|
19074
|
+
filePath: path19.relative(targetDir, match.data.path.text) || path19.basename(match.data.path.text),
|
|
16626
19075
|
lineNumber: match.data.line_number,
|
|
16627
19076
|
line: lineText
|
|
16628
19077
|
});
|
|
@@ -16700,8 +19149,8 @@ function createFindDefinitionTool() {
|
|
|
16700
19149
|
}
|
|
16701
19150
|
|
|
16702
19151
|
// src/tools/getFileStructure/index.ts
|
|
16703
|
-
import { existsSync as
|
|
16704
|
-
import
|
|
19152
|
+
import { existsSync as existsSync12, promises as fs14, statSync as statSync6 } from "fs";
|
|
19153
|
+
import path20 from "path";
|
|
16705
19154
|
|
|
16706
19155
|
// src/tools/getFileStructure/formatter.ts
|
|
16707
19156
|
var SECTIONS = [
|
|
@@ -16738,35 +19187,35 @@ function formatSection(lines, title, items, format) {
|
|
|
16738
19187
|
}
|
|
16739
19188
|
|
|
16740
19189
|
// src/tools/getFileStructure/index.ts
|
|
16741
|
-
var
|
|
19190
|
+
var MAX_FILE_SIZE4 = 10 * 1024 * 1024;
|
|
16742
19191
|
function createGetFileStructureTool() {
|
|
16743
19192
|
return {
|
|
16744
19193
|
toolFn: async (value) => {
|
|
16745
19194
|
const params = value;
|
|
16746
19195
|
try {
|
|
16747
19196
|
const cwd = process.cwd();
|
|
16748
|
-
const resolvedPath =
|
|
19197
|
+
const resolvedPath = path20.resolve(cwd, params.path);
|
|
16749
19198
|
if (!resolvedPath.startsWith(cwd)) {
|
|
16750
19199
|
return "Error: Access denied - cannot read files outside of current working directory";
|
|
16751
19200
|
}
|
|
16752
|
-
if (!
|
|
19201
|
+
if (!existsSync12(resolvedPath)) {
|
|
16753
19202
|
return `Error: File not found: ${params.path}`;
|
|
16754
19203
|
}
|
|
16755
19204
|
const stats = statSync6(resolvedPath);
|
|
16756
19205
|
if (stats.isDirectory()) {
|
|
16757
19206
|
return `Error: Path is a directory, not a file: ${params.path}`;
|
|
16758
19207
|
}
|
|
16759
|
-
if (stats.size >
|
|
16760
|
-
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`;
|
|
16761
19210
|
}
|
|
16762
|
-
const ext =
|
|
19211
|
+
const ext = path20.extname(resolvedPath).toLowerCase();
|
|
16763
19212
|
const { getLanguageForExtension, parseFileStructure, getSupportedLanguages } = await import("./treeSitterEngine-4SGFQDY3.js");
|
|
16764
19213
|
const languageId = getLanguageForExtension(ext);
|
|
16765
19214
|
if (!languageId) {
|
|
16766
19215
|
const supported = getSupportedLanguages();
|
|
16767
19216
|
return `Error: Unsupported file type "${ext}". Supported languages: ${supported.join(", ")}`;
|
|
16768
19217
|
}
|
|
16769
|
-
const sourceCode = await
|
|
19218
|
+
const sourceCode = await fs14.readFile(resolvedPath, "utf-8");
|
|
16770
19219
|
const lineCount = sourceCode.split("\n").length;
|
|
16771
19220
|
const items = await parseFileStructure(sourceCode, languageId);
|
|
16772
19221
|
return formatStructureOutput(params.path, items, stats.size, lineCount);
|
|
@@ -16827,7 +19276,9 @@ function CliApp() {
|
|
|
16827
19276
|
agentStore: null,
|
|
16828
19277
|
abortController: null,
|
|
16829
19278
|
contextContent: "",
|
|
16830
|
-
backgroundManager: null
|
|
19279
|
+
backgroundManager: null,
|
|
19280
|
+
wsManager: null,
|
|
19281
|
+
checkpointStore: null
|
|
16831
19282
|
});
|
|
16832
19283
|
const [isInitialized, setIsInitialized] = useState10(false);
|
|
16833
19284
|
const [initError, setInitError] = useState10(null);
|
|
@@ -16855,6 +19306,10 @@ function CliApp() {
|
|
|
16855
19306
|
})
|
|
16856
19307
|
);
|
|
16857
19308
|
}
|
|
19309
|
+
if (state.wsManager) {
|
|
19310
|
+
state.wsManager.disconnect();
|
|
19311
|
+
setWebSocketToolExecutor(null);
|
|
19312
|
+
}
|
|
16858
19313
|
if (state.agent) {
|
|
16859
19314
|
state.agent.removeAllListeners();
|
|
16860
19315
|
}
|
|
@@ -16873,9 +19328,14 @@ function CliApp() {
|
|
|
16873
19328
|
setTimeout(() => {
|
|
16874
19329
|
process.exit(0);
|
|
16875
19330
|
}, 100);
|
|
16876
|
-
}, [state.session, state.sessionStore, state.mcpManager, state.agent, state.imageStore]);
|
|
19331
|
+
}, [state.session, state.sessionStore, state.mcpManager, state.agent, state.imageStore, state.wsManager]);
|
|
16877
19332
|
useInput9((input, key) => {
|
|
16878
19333
|
if (key.escape) {
|
|
19334
|
+
const store = useCliStore.getState();
|
|
19335
|
+
if (store.pastedContent) {
|
|
19336
|
+
store.clearPaste();
|
|
19337
|
+
return;
|
|
19338
|
+
}
|
|
16879
19339
|
if (state.abortController) {
|
|
16880
19340
|
logger.debug("[ABORT] ESC pressed - aborting current operation...");
|
|
16881
19341
|
state.abortController.abort();
|
|
@@ -16893,9 +19353,9 @@ function CliApp() {
|
|
|
16893
19353
|
});
|
|
16894
19354
|
} else {
|
|
16895
19355
|
logger.debug("[EXIT] First Ctrl+C - press again to exit");
|
|
16896
|
-
const
|
|
16897
|
-
if (
|
|
16898
|
-
|
|
19356
|
+
const store = useCliStore.getState();
|
|
19357
|
+
if (store.inputValue.length > 0 || store.pastedContent) {
|
|
19358
|
+
store.clearInput();
|
|
16899
19359
|
}
|
|
16900
19360
|
exitTimestamp = now;
|
|
16901
19361
|
setExitRequested(true);
|
|
@@ -16943,7 +19403,7 @@ function CliApp() {
|
|
|
16943
19403
|
if (!isAuthenticated) {
|
|
16944
19404
|
console.log("\u2139\uFE0F AI features disabled. Available commands: /login, /help, /config\n");
|
|
16945
19405
|
const minimalSession = {
|
|
16946
|
-
id:
|
|
19406
|
+
id: uuidv413(),
|
|
16947
19407
|
name: `Session ${(/* @__PURE__ */ new Date()).toLocaleString()}`,
|
|
16948
19408
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
16949
19409
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -16971,10 +19431,45 @@ function CliApp() {
|
|
|
16971
19431
|
console.log(`\u{1F30D} API Environment: ${envName} (${apiBaseURL})`);
|
|
16972
19432
|
}
|
|
16973
19433
|
const apiClient = new ApiClient(apiBaseURL, state.configStore);
|
|
16974
|
-
const
|
|
16975
|
-
|
|
16976
|
-
|
|
16977
|
-
}
|
|
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
|
+
}
|
|
16978
19473
|
const models = await llm.getModelInfo();
|
|
16979
19474
|
if (models.length === 0) {
|
|
16980
19475
|
throw new Error("No models available from server.");
|
|
@@ -16987,7 +19482,7 @@ function CliApp() {
|
|
|
16987
19482
|
}
|
|
16988
19483
|
llm.currentModel = modelInfo.id;
|
|
16989
19484
|
const newSession = {
|
|
16990
|
-
id:
|
|
19485
|
+
id: uuidv413(),
|
|
16991
19486
|
name: `Session ${(/* @__PURE__ */ new Date()).toLocaleString()}`,
|
|
16992
19487
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
16993
19488
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -17037,6 +19532,12 @@ function CliApp() {
|
|
|
17037
19532
|
enqueuePermissionPrompt(prompt);
|
|
17038
19533
|
});
|
|
17039
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
|
+
}
|
|
17040
19541
|
const agentContext = {
|
|
17041
19542
|
currentAgent: null,
|
|
17042
19543
|
observationQueue: []
|
|
@@ -17049,7 +19550,10 @@ function CliApp() {
|
|
|
17049
19550
|
promptFn,
|
|
17050
19551
|
agentContext,
|
|
17051
19552
|
state.configStore,
|
|
17052
|
-
apiClient
|
|
19553
|
+
apiClient,
|
|
19554
|
+
void 0,
|
|
19555
|
+
// toolFilter
|
|
19556
|
+
checkpointStore
|
|
17053
19557
|
);
|
|
17054
19558
|
startupLog.push(`\u{1F6E0}\uFE0F Loaded ${b4mTools2.length} B4M tool(s)`);
|
|
17055
19559
|
const mcpManager = new McpManager(config);
|
|
@@ -17080,7 +19584,8 @@ function CliApp() {
|
|
|
17080
19584
|
apiClient,
|
|
17081
19585
|
agentStore,
|
|
17082
19586
|
customCommandStore: state.customCommandStore,
|
|
17083
|
-
enableParallelToolExecution: config.preferences.enableParallelToolExecution === true
|
|
19587
|
+
enableParallelToolExecution: config.preferences.enableParallelToolExecution === true,
|
|
19588
|
+
checkpointStore
|
|
17084
19589
|
});
|
|
17085
19590
|
const backgroundManager = new BackgroundAgentManager(orchestrator);
|
|
17086
19591
|
backgroundManager.setOnStatusChange((job) => {
|
|
@@ -17201,8 +19706,12 @@ function CliApp() {
|
|
|
17201
19706
|
// Store agent store for agent management commands
|
|
17202
19707
|
contextContent: contextResult.mergedContent,
|
|
17203
19708
|
// Store raw context for compact instructions
|
|
17204
|
-
backgroundManager
|
|
19709
|
+
backgroundManager,
|
|
17205
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
|
|
17206
19715
|
}));
|
|
17207
19716
|
setStoreSession(newSession);
|
|
17208
19717
|
const bannerLines = [
|
|
@@ -17297,13 +19806,13 @@ function CliApp() {
|
|
|
17297
19806
|
messageContent = multimodalMessage.content;
|
|
17298
19807
|
}
|
|
17299
19808
|
const userMessage = {
|
|
17300
|
-
id:
|
|
19809
|
+
id: uuidv413(),
|
|
17301
19810
|
role: "user",
|
|
17302
19811
|
content: userMessageContent,
|
|
17303
19812
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
17304
19813
|
};
|
|
17305
19814
|
const pendingAssistantMessage = {
|
|
17306
|
-
id:
|
|
19815
|
+
id: uuidv413(),
|
|
17307
19816
|
role: "assistant",
|
|
17308
19817
|
content: "...",
|
|
17309
19818
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -17516,13 +20025,13 @@ function CliApp() {
|
|
|
17516
20025
|
userMessageContent = message;
|
|
17517
20026
|
}
|
|
17518
20027
|
const userMessage = {
|
|
17519
|
-
id:
|
|
20028
|
+
id: uuidv413(),
|
|
17520
20029
|
role: "user",
|
|
17521
20030
|
content: userMessageContent,
|
|
17522
20031
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
17523
20032
|
};
|
|
17524
20033
|
const pendingAssistantMessage = {
|
|
17525
|
-
id:
|
|
20034
|
+
id: uuidv413(),
|
|
17526
20035
|
role: "assistant",
|
|
17527
20036
|
content: "...",
|
|
17528
20037
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -17602,7 +20111,7 @@ function CliApp() {
|
|
|
17602
20111
|
const currentSession = useCliStore.getState().session;
|
|
17603
20112
|
if (currentSession) {
|
|
17604
20113
|
const cancelMessage = {
|
|
17605
|
-
id:
|
|
20114
|
+
id: uuidv413(),
|
|
17606
20115
|
role: "assistant",
|
|
17607
20116
|
content: "\u26A0\uFE0F Operation cancelled by user",
|
|
17608
20117
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -17647,7 +20156,7 @@ function CliApp() {
|
|
|
17647
20156
|
setState((prev) => ({ ...prev, abortController }));
|
|
17648
20157
|
try {
|
|
17649
20158
|
const pendingAssistantMessage = {
|
|
17650
|
-
id:
|
|
20159
|
+
id: uuidv413(),
|
|
17651
20160
|
role: "assistant",
|
|
17652
20161
|
content: "...",
|
|
17653
20162
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -17675,7 +20184,7 @@ function CliApp() {
|
|
|
17675
20184
|
const currentSession = useCliStore.getState().session;
|
|
17676
20185
|
if (!currentSession) return;
|
|
17677
20186
|
const continuationMessage = {
|
|
17678
|
-
id:
|
|
20187
|
+
id: uuidv413(),
|
|
17679
20188
|
role: "assistant",
|
|
17680
20189
|
content: "---\n\n**Background Agent Results:**\n\n" + result.finalAnswer,
|
|
17681
20190
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -17736,13 +20245,13 @@ function CliApp() {
|
|
|
17736
20245
|
isError = true;
|
|
17737
20246
|
}
|
|
17738
20247
|
const userMessage = {
|
|
17739
|
-
id:
|
|
20248
|
+
id: uuidv413(),
|
|
17740
20249
|
role: "user",
|
|
17741
20250
|
content: `$ ${command}`,
|
|
17742
20251
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
17743
20252
|
};
|
|
17744
20253
|
const assistantMessage = {
|
|
17745
|
-
id:
|
|
20254
|
+
id: uuidv413(),
|
|
17746
20255
|
role: "assistant",
|
|
17747
20256
|
content: isError ? `\u274C Error:
|
|
17748
20257
|
${output}` : output.trim() || "(no output)",
|
|
@@ -17895,6 +20404,10 @@ Available commands:
|
|
|
17895
20404
|
/exit - Exit the CLI
|
|
17896
20405
|
/clear - Start a new session
|
|
17897
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
|
|
17898
20411
|
/login - Authenticate with your B4M account
|
|
17899
20412
|
/logout - Clear authentication and sign out
|
|
17900
20413
|
/whoami - Show current authenticated user
|
|
@@ -17987,6 +20500,9 @@ Keyboard Shortcuts:
|
|
|
17987
20500
|
}
|
|
17988
20501
|
await logger.initialize(loadedSession.id);
|
|
17989
20502
|
logger.debug("=== Session Resumed ===");
|
|
20503
|
+
if (state.checkpointStore) {
|
|
20504
|
+
state.checkpointStore.setSessionId(loadedSession.id);
|
|
20505
|
+
}
|
|
17990
20506
|
setState((prev) => ({ ...prev, session: loadedSession }));
|
|
17991
20507
|
setStoreSession(loadedSession);
|
|
17992
20508
|
useCliStore.getState().clearPendingMessages();
|
|
@@ -18211,7 +20727,7 @@ Keyboard Shortcuts:
|
|
|
18211
20727
|
console.clear();
|
|
18212
20728
|
const model = state.session?.model || state.config?.defaultModel || "claude-sonnet";
|
|
18213
20729
|
const newSession = {
|
|
18214
|
-
id:
|
|
20730
|
+
id: uuidv413(),
|
|
18215
20731
|
name: `Session ${(/* @__PURE__ */ new Date()).toLocaleString()}`,
|
|
18216
20732
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
18217
20733
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -18225,6 +20741,9 @@ Keyboard Shortcuts:
|
|
|
18225
20741
|
};
|
|
18226
20742
|
await logger.initialize(newSession.id);
|
|
18227
20743
|
logger.debug("=== New Session Started via /clear ===");
|
|
20744
|
+
if (state.checkpointStore) {
|
|
20745
|
+
state.checkpointStore.setSessionId(newSession.id);
|
|
20746
|
+
}
|
|
18228
20747
|
setState((prev) => ({ ...prev, session: newSession }));
|
|
18229
20748
|
setStoreSession(newSession);
|
|
18230
20749
|
useCliStore.getState().clearPendingMessages();
|
|
@@ -18235,8 +20754,7 @@ Keyboard Shortcuts:
|
|
|
18235
20754
|
`);
|
|
18236
20755
|
break;
|
|
18237
20756
|
}
|
|
18238
|
-
case "rewind":
|
|
18239
|
-
case "undo": {
|
|
20757
|
+
case "rewind": {
|
|
18240
20758
|
if (!state.session) {
|
|
18241
20759
|
console.log("No active session to rewind");
|
|
18242
20760
|
return;
|
|
@@ -18295,6 +20813,103 @@ Keyboard Shortcuts:
|
|
|
18295
20813
|
}));
|
|
18296
20814
|
break;
|
|
18297
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
|
+
}
|
|
18298
20913
|
case "usage": {
|
|
18299
20914
|
const usageAuthTokens = await state.configStore.getAuthTokens();
|
|
18300
20915
|
if (!usageAuthTokens || new Date(usageAuthTokens.expiresAt) <= /* @__PURE__ */ new Date()) {
|
|
@@ -18716,9 +21331,9 @@ No usage data available for the last ${USAGE_DAYS} days.`);
|
|
|
18716
21331
|
return { ...prev, config: updatedConfig };
|
|
18717
21332
|
});
|
|
18718
21333
|
if (modelChanged && state.agent) {
|
|
18719
|
-
const
|
|
18720
|
-
if (
|
|
18721
|
-
|
|
21334
|
+
const backend = state.agent.context.llm;
|
|
21335
|
+
if (backend) {
|
|
21336
|
+
backend.currentModel = updatedConfig.defaultModel;
|
|
18722
21337
|
}
|
|
18723
21338
|
}
|
|
18724
21339
|
};
|