@bike4mind/cli 0.2.31-update-default-agent-models.19589 → 0.2.31
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/agents/defaults/explore.md +4 -7
- package/dist/agents/defaults/plan.md +4 -7
- package/dist/agents/defaults/review.md +3 -6
- package/dist/agents/defaults/test.md +4 -7
- package/dist/{chunk-S6AUIWWB.js → chunk-4R42FAEH.js} +1 -1
- package/dist/{chunk-DQHCE3TN.js → chunk-BD56DFFM.js} +1 -1
- package/dist/{chunk-4P27WV34.js → chunk-C7LKV7P2.js} +1 -1
- package/dist/{chunk-SB53O27R.js → chunk-PAE3OMY2.js} +6 -6
- package/dist/{chunk-PV6PZFPC.js → chunk-UAJBCJZ2.js} +16 -18
- package/dist/commands/doctorCommand.js +1 -1
- package/dist/commands/updateCommand.js +1 -1
- package/dist/{create-PVJSCD5H.js → create-3D6BIWBM.js} +2 -2
- package/dist/index.js +744 -208
- package/dist/{mementoService-RDPCVXWF.js → mementoService-3GQOTWXQ.js} +2 -2
- package/dist/{src-DCF72PTU.js → src-FO2GDFVW.js} +1 -1
- package/dist/{subtractCredits-K4PWRUF4.js → subtractCredits-DXBZDEAF.js} +2 -2
- package/package.json +6 -6
package/dist/index.js
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import "./chunk-GQGOWACU.js";
|
|
3
|
-
import "./chunk-
|
|
4
|
-
import "./chunk-
|
|
3
|
+
import "./chunk-C7LKV7P2.js";
|
|
4
|
+
import "./chunk-4R42FAEH.js";
|
|
5
5
|
import "./chunk-BPFEGDC7.js";
|
|
6
6
|
import "./chunk-BDQBOLYG.js";
|
|
7
7
|
import {
|
|
8
8
|
getEffectiveApiKey,
|
|
9
9
|
getOpenWeatherKey,
|
|
10
10
|
getSerperKey
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-BD56DFFM.js";
|
|
12
12
|
import {
|
|
13
13
|
ConfigStore,
|
|
14
14
|
logger
|
|
@@ -16,7 +16,7 @@ import {
|
|
|
16
16
|
import {
|
|
17
17
|
checkForUpdate,
|
|
18
18
|
package_default
|
|
19
|
-
} from "./chunk-
|
|
19
|
+
} from "./chunk-PAE3OMY2.js";
|
|
20
20
|
import {
|
|
21
21
|
selectActiveBackgroundAgents,
|
|
22
22
|
useCliStore
|
|
@@ -32,7 +32,7 @@ import {
|
|
|
32
32
|
OpenAIBackend,
|
|
33
33
|
OpenAIImageService,
|
|
34
34
|
XAIImageService
|
|
35
|
-
} from "./chunk-
|
|
35
|
+
} from "./chunk-UAJBCJZ2.js";
|
|
36
36
|
import {
|
|
37
37
|
AiEvents,
|
|
38
38
|
ApiKeyEvents,
|
|
@@ -733,8 +733,25 @@ var COMMANDS = [
|
|
|
733
733
|
},
|
|
734
734
|
{
|
|
735
735
|
name: "rewind",
|
|
736
|
-
description: "Rewind conversation to a previous point"
|
|
737
|
-
|
|
736
|
+
description: "Rewind conversation to a previous point"
|
|
737
|
+
},
|
|
738
|
+
{
|
|
739
|
+
name: "undo",
|
|
740
|
+
description: "Undo the last file change"
|
|
741
|
+
},
|
|
742
|
+
{
|
|
743
|
+
name: "checkpoints",
|
|
744
|
+
description: "List available file restore points"
|
|
745
|
+
},
|
|
746
|
+
{
|
|
747
|
+
name: "restore",
|
|
748
|
+
description: "Restore files to a specific checkpoint",
|
|
749
|
+
args: "<number>"
|
|
750
|
+
},
|
|
751
|
+
{
|
|
752
|
+
name: "diff",
|
|
753
|
+
description: "Show diff between current state and a checkpoint",
|
|
754
|
+
args: "[number]"
|
|
738
755
|
},
|
|
739
756
|
{
|
|
740
757
|
name: "project-config",
|
|
@@ -2838,9 +2855,346 @@ var CommandHistoryStore = class {
|
|
|
2838
2855
|
}
|
|
2839
2856
|
};
|
|
2840
2857
|
|
|
2841
|
-
// src/storage/
|
|
2842
|
-
import fs5 from "fs
|
|
2858
|
+
// src/storage/CheckpointStore.ts
|
|
2859
|
+
import { promises as fs5 } from "fs";
|
|
2860
|
+
import { existsSync as existsSync4, readFileSync as readFileSync4, writeFileSync, unlinkSync } from "fs";
|
|
2861
|
+
import { execFileSync } from "child_process";
|
|
2843
2862
|
import path6 from "path";
|
|
2863
|
+
var MAX_FILE_SIZE2 = 10 * 1024 * 1024;
|
|
2864
|
+
var DEFAULT_KEEP_COUNT = 50;
|
|
2865
|
+
var ABSENT_MARKER = ".b4m-absent";
|
|
2866
|
+
var CheckpointStore = class {
|
|
2867
|
+
constructor(projectDir) {
|
|
2868
|
+
this.metadata = null;
|
|
2869
|
+
this.sessionId = null;
|
|
2870
|
+
this.initialized = false;
|
|
2871
|
+
this.projectDir = projectDir;
|
|
2872
|
+
this.shadowRepoDir = path6.join(projectDir, ".b4m", "shadow-repo");
|
|
2873
|
+
this.metadataPath = path6.join(projectDir, ".b4m", "checkpoints.json");
|
|
2874
|
+
}
|
|
2875
|
+
/**
|
|
2876
|
+
* Initialize the shadow git repository and load metadata
|
|
2877
|
+
*/
|
|
2878
|
+
async init(sessionId) {
|
|
2879
|
+
this.sessionId = sessionId;
|
|
2880
|
+
await fs5.mkdir(path6.join(this.projectDir, ".b4m"), { recursive: true });
|
|
2881
|
+
if (!existsSync4(path6.join(this.shadowRepoDir, ".git"))) {
|
|
2882
|
+
await fs5.mkdir(this.shadowRepoDir, { recursive: true });
|
|
2883
|
+
this.git("init");
|
|
2884
|
+
this.git("config", "user.email", "checkpoint@b4m.local");
|
|
2885
|
+
this.git("config", "user.name", "B4M Checkpoint");
|
|
2886
|
+
this.git("commit", "--allow-empty", "-m", "checkpoint-init");
|
|
2887
|
+
}
|
|
2888
|
+
await this.loadMetadata();
|
|
2889
|
+
await this.ensureGitignore();
|
|
2890
|
+
await this.pruneCheckpoints(DEFAULT_KEEP_COUNT);
|
|
2891
|
+
this.initialized = true;
|
|
2892
|
+
}
|
|
2893
|
+
/**
|
|
2894
|
+
* Update session ID (e.g., on /clear or /resume)
|
|
2895
|
+
*/
|
|
2896
|
+
setSessionId(sessionId) {
|
|
2897
|
+
this.sessionId = sessionId;
|
|
2898
|
+
}
|
|
2899
|
+
/**
|
|
2900
|
+
* Create a checkpoint by snapshotting the current state of target files
|
|
2901
|
+
* before they are modified by a tool.
|
|
2902
|
+
*
|
|
2903
|
+
* @param toolName - The tool about to modify files
|
|
2904
|
+
* @param filePaths - Relative paths of files about to be modified
|
|
2905
|
+
* @param name - Optional human-readable checkpoint name
|
|
2906
|
+
*/
|
|
2907
|
+
async createCheckpoint(toolName, filePaths, name) {
|
|
2908
|
+
if (!this.initialized || !this.sessionId) {
|
|
2909
|
+
return null;
|
|
2910
|
+
}
|
|
2911
|
+
const checkpointName = name || `before-${toolName}-${path6.basename(filePaths[0] || "unknown")}`;
|
|
2912
|
+
try {
|
|
2913
|
+
let hasChanges = false;
|
|
2914
|
+
for (const filePath of filePaths) {
|
|
2915
|
+
const absolutePath = this.validatePathWithinProject(filePath);
|
|
2916
|
+
const shadowPath = path6.join(this.shadowRepoDir, filePath);
|
|
2917
|
+
const shadowDir = path6.dirname(shadowPath);
|
|
2918
|
+
const absentMarkerPath = path6.join(shadowDir, `${path6.basename(filePath)}${ABSENT_MARKER}`);
|
|
2919
|
+
await fs5.mkdir(shadowDir, { recursive: true });
|
|
2920
|
+
if (existsSync4(absolutePath)) {
|
|
2921
|
+
const stats = await fs5.lstat(absolutePath);
|
|
2922
|
+
if (stats.isSymbolicLink()) {
|
|
2923
|
+
continue;
|
|
2924
|
+
}
|
|
2925
|
+
if (stats.size > MAX_FILE_SIZE2) {
|
|
2926
|
+
continue;
|
|
2927
|
+
}
|
|
2928
|
+
if (existsSync4(absentMarkerPath)) {
|
|
2929
|
+
await fs5.unlink(absentMarkerPath);
|
|
2930
|
+
}
|
|
2931
|
+
await fs5.copyFile(absolutePath, shadowPath);
|
|
2932
|
+
hasChanges = true;
|
|
2933
|
+
} else {
|
|
2934
|
+
if (existsSync4(shadowPath)) {
|
|
2935
|
+
await fs5.unlink(shadowPath);
|
|
2936
|
+
}
|
|
2937
|
+
await fs5.writeFile(absentMarkerPath, "", "utf-8");
|
|
2938
|
+
hasChanges = true;
|
|
2939
|
+
}
|
|
2940
|
+
}
|
|
2941
|
+
if (!hasChanges) {
|
|
2942
|
+
return null;
|
|
2943
|
+
}
|
|
2944
|
+
this.git("add", "-A");
|
|
2945
|
+
try {
|
|
2946
|
+
this.git("diff", "--cached", "--quiet");
|
|
2947
|
+
return null;
|
|
2948
|
+
} catch {
|
|
2949
|
+
}
|
|
2950
|
+
this.git("commit", "-m", checkpointName);
|
|
2951
|
+
const sha = this.git("rev-parse", "--short", "HEAD").trim();
|
|
2952
|
+
const checkpoint = {
|
|
2953
|
+
id: sha,
|
|
2954
|
+
name: checkpointName,
|
|
2955
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2956
|
+
toolName,
|
|
2957
|
+
filePaths: [...filePaths],
|
|
2958
|
+
sessionId: this.sessionId
|
|
2959
|
+
};
|
|
2960
|
+
if (!this.metadata) {
|
|
2961
|
+
this.metadata = { checkpoints: [], createdAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
2962
|
+
}
|
|
2963
|
+
this.metadata.checkpoints.push(checkpoint);
|
|
2964
|
+
await this.saveMetadata();
|
|
2965
|
+
return checkpoint;
|
|
2966
|
+
} catch {
|
|
2967
|
+
return null;
|
|
2968
|
+
}
|
|
2969
|
+
}
|
|
2970
|
+
/**
|
|
2971
|
+
* List checkpoints for the current session (most recent first)
|
|
2972
|
+
*/
|
|
2973
|
+
listCheckpoints() {
|
|
2974
|
+
if (!this.metadata || !this.sessionId) {
|
|
2975
|
+
return [];
|
|
2976
|
+
}
|
|
2977
|
+
return this.metadata.checkpoints.filter((cp) => cp.sessionId === this.sessionId).reverse();
|
|
2978
|
+
}
|
|
2979
|
+
/**
|
|
2980
|
+
* Get a specific checkpoint by 1-based index (1 = most recent)
|
|
2981
|
+
*/
|
|
2982
|
+
getCheckpoint(index) {
|
|
2983
|
+
const checkpoints = this.listCheckpoints();
|
|
2984
|
+
if (index < 1 || index > checkpoints.length) {
|
|
2985
|
+
return null;
|
|
2986
|
+
}
|
|
2987
|
+
return checkpoints[index - 1];
|
|
2988
|
+
}
|
|
2989
|
+
/**
|
|
2990
|
+
* Restore files to the state captured in a specific checkpoint
|
|
2991
|
+
*
|
|
2992
|
+
* @param index - 1-based index (1 = most recent)
|
|
2993
|
+
* @returns The checkpoint that was restored to
|
|
2994
|
+
*/
|
|
2995
|
+
async restoreCheckpoint(index) {
|
|
2996
|
+
const checkpoint = this.getCheckpoint(index);
|
|
2997
|
+
if (!checkpoint) {
|
|
2998
|
+
throw new Error(`Checkpoint #${index} not found. Use /checkpoints to see available restore points.`);
|
|
2999
|
+
}
|
|
3000
|
+
for (const filePath of checkpoint.filePaths) {
|
|
3001
|
+
const absolutePath = this.validatePathWithinProject(filePath);
|
|
3002
|
+
try {
|
|
3003
|
+
const content = this.git("show", `${checkpoint.id}:${filePath}`);
|
|
3004
|
+
await fs5.mkdir(path6.dirname(absolutePath), { recursive: true });
|
|
3005
|
+
await fs5.writeFile(absolutePath, content, "utf-8");
|
|
3006
|
+
} catch {
|
|
3007
|
+
try {
|
|
3008
|
+
this.git("show", `${checkpoint.id}:${path6.dirname(filePath)}/${path6.basename(filePath)}${ABSENT_MARKER}`);
|
|
3009
|
+
if (existsSync4(absolutePath)) {
|
|
3010
|
+
await fs5.unlink(absolutePath);
|
|
3011
|
+
}
|
|
3012
|
+
} catch {
|
|
3013
|
+
}
|
|
3014
|
+
}
|
|
3015
|
+
}
|
|
3016
|
+
return checkpoint;
|
|
3017
|
+
}
|
|
3018
|
+
/**
|
|
3019
|
+
* Undo the last file change (restore to most recent checkpoint)
|
|
3020
|
+
*/
|
|
3021
|
+
async undoLast() {
|
|
3022
|
+
return this.restoreCheckpoint(1);
|
|
3023
|
+
}
|
|
3024
|
+
/**
|
|
3025
|
+
* Get diff between current file state and a checkpoint
|
|
3026
|
+
*
|
|
3027
|
+
* @param index - 1-based index (1 = most recent, default)
|
|
3028
|
+
* @returns Unified diff string
|
|
3029
|
+
*/
|
|
3030
|
+
getCheckpointDiff(index = 1) {
|
|
3031
|
+
const checkpoint = this.getCheckpoint(index);
|
|
3032
|
+
if (!checkpoint) {
|
|
3033
|
+
throw new Error(`Checkpoint #${index} not found. Use /checkpoints to see available restore points.`);
|
|
3034
|
+
}
|
|
3035
|
+
const diffParts = [];
|
|
3036
|
+
for (const filePath of checkpoint.filePaths) {
|
|
3037
|
+
const absolutePath = this.validatePathWithinProject(filePath);
|
|
3038
|
+
const tmpCheckpoint = path6.join(this.shadowRepoDir, ".diff-a");
|
|
3039
|
+
const tmpCurrent = path6.join(this.shadowRepoDir, ".diff-b");
|
|
3040
|
+
try {
|
|
3041
|
+
let checkpointContent;
|
|
3042
|
+
try {
|
|
3043
|
+
checkpointContent = this.git("show", `${checkpoint.id}:${filePath}`);
|
|
3044
|
+
} catch {
|
|
3045
|
+
checkpointContent = "";
|
|
3046
|
+
}
|
|
3047
|
+
let currentContent = "";
|
|
3048
|
+
if (existsSync4(absolutePath)) {
|
|
3049
|
+
currentContent = readFileSync4(absolutePath, "utf-8");
|
|
3050
|
+
}
|
|
3051
|
+
if (checkpointContent === currentContent) {
|
|
3052
|
+
continue;
|
|
3053
|
+
}
|
|
3054
|
+
writeFileSync(tmpCheckpoint, checkpointContent, "utf-8");
|
|
3055
|
+
writeFileSync(tmpCurrent, currentContent, "utf-8");
|
|
3056
|
+
try {
|
|
3057
|
+
this.git(
|
|
3058
|
+
"diff",
|
|
3059
|
+
"--no-index",
|
|
3060
|
+
"--color",
|
|
3061
|
+
`--src-prefix=checkpoint:`,
|
|
3062
|
+
`--dst-prefix=current:`,
|
|
3063
|
+
tmpCheckpoint,
|
|
3064
|
+
tmpCurrent
|
|
3065
|
+
);
|
|
3066
|
+
} catch (diffError) {
|
|
3067
|
+
if (diffError && typeof diffError === "object" && "stdout" in diffError) {
|
|
3068
|
+
const output = diffError.stdout?.toString() || "";
|
|
3069
|
+
if (output) {
|
|
3070
|
+
diffParts.push(`--- ${filePath} (checkpoint #${index})
|
|
3071
|
+
+++ ${filePath} (current)
|
|
3072
|
+
${output}`);
|
|
3073
|
+
}
|
|
3074
|
+
}
|
|
3075
|
+
}
|
|
3076
|
+
} catch {
|
|
3077
|
+
} finally {
|
|
3078
|
+
try {
|
|
3079
|
+
unlinkSync(tmpCheckpoint);
|
|
3080
|
+
} catch {
|
|
3081
|
+
}
|
|
3082
|
+
try {
|
|
3083
|
+
unlinkSync(tmpCurrent);
|
|
3084
|
+
} catch {
|
|
3085
|
+
}
|
|
3086
|
+
}
|
|
3087
|
+
}
|
|
3088
|
+
return diffParts.join("\n");
|
|
3089
|
+
}
|
|
3090
|
+
/**
|
|
3091
|
+
* Prune old checkpoints beyond the keep count
|
|
3092
|
+
*/
|
|
3093
|
+
async pruneCheckpoints(keepCount = DEFAULT_KEEP_COUNT) {
|
|
3094
|
+
if (!this.metadata) return;
|
|
3095
|
+
const total = this.metadata.checkpoints.length;
|
|
3096
|
+
if (total <= keepCount) return;
|
|
3097
|
+
this.metadata.checkpoints = this.metadata.checkpoints.slice(-keepCount);
|
|
3098
|
+
await this.saveMetadata();
|
|
3099
|
+
try {
|
|
3100
|
+
this.git("gc", "--auto", "--quiet");
|
|
3101
|
+
} catch {
|
|
3102
|
+
}
|
|
3103
|
+
}
|
|
3104
|
+
/**
|
|
3105
|
+
* Clean up the shadow repository entirely
|
|
3106
|
+
*/
|
|
3107
|
+
async cleanup() {
|
|
3108
|
+
try {
|
|
3109
|
+
await fs5.rm(this.shadowRepoDir, { recursive: true, force: true });
|
|
3110
|
+
if (existsSync4(this.metadataPath)) {
|
|
3111
|
+
await fs5.unlink(this.metadataPath);
|
|
3112
|
+
}
|
|
3113
|
+
this.metadata = null;
|
|
3114
|
+
this.initialized = false;
|
|
3115
|
+
} catch {
|
|
3116
|
+
}
|
|
3117
|
+
}
|
|
3118
|
+
// --- Private helpers ---
|
|
3119
|
+
/**
|
|
3120
|
+
* Validate that a file path resolves within the project directory.
|
|
3121
|
+
* Prevents path traversal attacks (e.g., ../../etc/passwd).
|
|
3122
|
+
*/
|
|
3123
|
+
validatePathWithinProject(filePath) {
|
|
3124
|
+
const absolutePath = path6.resolve(this.projectDir, filePath);
|
|
3125
|
+
const normalizedProject = path6.resolve(this.projectDir) + path6.sep;
|
|
3126
|
+
if (!absolutePath.startsWith(normalizedProject) && absolutePath !== path6.resolve(this.projectDir)) {
|
|
3127
|
+
throw new Error(`Path traversal detected: ${filePath}`);
|
|
3128
|
+
}
|
|
3129
|
+
return absolutePath;
|
|
3130
|
+
}
|
|
3131
|
+
/**
|
|
3132
|
+
* Execute a git command in the shadow repo
|
|
3133
|
+
*/
|
|
3134
|
+
git(...args) {
|
|
3135
|
+
return execFileSync("git", args, {
|
|
3136
|
+
cwd: this.shadowRepoDir,
|
|
3137
|
+
encoding: "utf-8",
|
|
3138
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
3139
|
+
timeout: 1e4
|
|
3140
|
+
});
|
|
3141
|
+
}
|
|
3142
|
+
/**
|
|
3143
|
+
* Load checkpoint metadata from disk
|
|
3144
|
+
*/
|
|
3145
|
+
async loadMetadata() {
|
|
3146
|
+
try {
|
|
3147
|
+
if (existsSync4(this.metadataPath)) {
|
|
3148
|
+
const data = await fs5.readFile(this.metadataPath, "utf-8");
|
|
3149
|
+
this.metadata = JSON.parse(data);
|
|
3150
|
+
} else {
|
|
3151
|
+
this.metadata = {
|
|
3152
|
+
checkpoints: [],
|
|
3153
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3154
|
+
};
|
|
3155
|
+
}
|
|
3156
|
+
} catch {
|
|
3157
|
+
this.metadata = {
|
|
3158
|
+
checkpoints: [],
|
|
3159
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3160
|
+
};
|
|
3161
|
+
}
|
|
3162
|
+
}
|
|
3163
|
+
/**
|
|
3164
|
+
* Save checkpoint metadata to disk
|
|
3165
|
+
*/
|
|
3166
|
+
async saveMetadata() {
|
|
3167
|
+
if (!this.metadata) return;
|
|
3168
|
+
await fs5.writeFile(this.metadataPath, JSON.stringify(this.metadata, null, 2), "utf-8");
|
|
3169
|
+
}
|
|
3170
|
+
/**
|
|
3171
|
+
* Ensure .b4m/ is in .gitignore
|
|
3172
|
+
*/
|
|
3173
|
+
async ensureGitignore() {
|
|
3174
|
+
const gitignorePath = path6.join(this.projectDir, ".gitignore");
|
|
3175
|
+
const entryToAdd = ".b4m/";
|
|
3176
|
+
try {
|
|
3177
|
+
let content = "";
|
|
3178
|
+
try {
|
|
3179
|
+
content = await fs5.readFile(gitignorePath, "utf-8");
|
|
3180
|
+
} catch {
|
|
3181
|
+
}
|
|
3182
|
+
if (content.includes(entryToAdd) || content.includes(".b4m")) {
|
|
3183
|
+
return;
|
|
3184
|
+
}
|
|
3185
|
+
const newContent = content.trim() + (content ? "\n" : "") + `
|
|
3186
|
+
# B4M checkpoint data
|
|
3187
|
+
${entryToAdd}
|
|
3188
|
+
`;
|
|
3189
|
+
await fs5.writeFile(gitignorePath, newContent, "utf-8");
|
|
3190
|
+
} catch {
|
|
3191
|
+
}
|
|
3192
|
+
}
|
|
3193
|
+
};
|
|
3194
|
+
|
|
3195
|
+
// src/storage/CustomCommandStore.ts
|
|
3196
|
+
import fs6 from "fs/promises";
|
|
3197
|
+
import path7 from "path";
|
|
2844
3198
|
import os from "os";
|
|
2845
3199
|
|
|
2846
3200
|
// src/utils/commandParser.ts
|
|
@@ -3004,14 +3358,14 @@ var CustomCommandStore = class {
|
|
|
3004
3358
|
const home = os.homedir();
|
|
3005
3359
|
const root = projectRoot || process.cwd();
|
|
3006
3360
|
this.globalCommandsDirs = [
|
|
3007
|
-
|
|
3008
|
-
|
|
3009
|
-
|
|
3361
|
+
path7.join(home, ".bike4mind", "commands"),
|
|
3362
|
+
path7.join(home, ".claude", "commands"),
|
|
3363
|
+
path7.join(home, ".claude", "skills")
|
|
3010
3364
|
];
|
|
3011
3365
|
this.projectCommandsDirs = [
|
|
3012
|
-
|
|
3013
|
-
|
|
3014
|
-
|
|
3366
|
+
path7.join(root, ".bike4mind", "commands"),
|
|
3367
|
+
path7.join(root, ".claude", "commands"),
|
|
3368
|
+
path7.join(root, ".claude", "skills")
|
|
3015
3369
|
];
|
|
3016
3370
|
}
|
|
3017
3371
|
/**
|
|
@@ -3036,7 +3390,7 @@ var CustomCommandStore = class {
|
|
|
3036
3390
|
*/
|
|
3037
3391
|
async loadCommandsFromDirectory(directory, source) {
|
|
3038
3392
|
try {
|
|
3039
|
-
const stats = await
|
|
3393
|
+
const stats = await fs6.stat(directory);
|
|
3040
3394
|
if (!stats.isDirectory()) {
|
|
3041
3395
|
return;
|
|
3042
3396
|
}
|
|
@@ -3069,9 +3423,9 @@ var CustomCommandStore = class {
|
|
|
3069
3423
|
async findCommandFiles(directory) {
|
|
3070
3424
|
const files = [];
|
|
3071
3425
|
try {
|
|
3072
|
-
const entries = await
|
|
3426
|
+
const entries = await fs6.readdir(directory, { withFileTypes: true });
|
|
3073
3427
|
for (const entry of entries) {
|
|
3074
|
-
const fullPath =
|
|
3428
|
+
const fullPath = path7.join(directory, entry.name);
|
|
3075
3429
|
if (entry.isDirectory()) {
|
|
3076
3430
|
const subFiles = await this.findCommandFiles(fullPath);
|
|
3077
3431
|
files.push(...subFiles);
|
|
@@ -3091,14 +3445,14 @@ var CustomCommandStore = class {
|
|
|
3091
3445
|
* @param source - Source identifier ('global' or 'project')
|
|
3092
3446
|
*/
|
|
3093
3447
|
async loadCommandFile(filePath, source) {
|
|
3094
|
-
const filename =
|
|
3448
|
+
const filename = path7.basename(filePath);
|
|
3095
3449
|
const isSkillFile = filename.toLowerCase() === "skill.md";
|
|
3096
3450
|
const commandName = isSkillFile ? this.extractSkillName(filePath) : extractCommandName(filename);
|
|
3097
3451
|
if (!commandName) {
|
|
3098
3452
|
console.warn(`Invalid command filename: ${filename} (must end with .md and have valid name)`);
|
|
3099
3453
|
return;
|
|
3100
3454
|
}
|
|
3101
|
-
const fileContent = await
|
|
3455
|
+
const fileContent = await fs6.readFile(filePath, "utf-8");
|
|
3102
3456
|
const command = parseCommandFile(fileContent, filePath, commandName, source);
|
|
3103
3457
|
this.commands.set(commandName, command);
|
|
3104
3458
|
}
|
|
@@ -3110,7 +3464,7 @@ var CustomCommandStore = class {
|
|
|
3110
3464
|
* @returns Skill name or null if invalid
|
|
3111
3465
|
*/
|
|
3112
3466
|
extractSkillName(filePath) {
|
|
3113
|
-
const parentDir =
|
|
3467
|
+
const parentDir = path7.basename(path7.dirname(filePath));
|
|
3114
3468
|
return parentDir && parentDir !== "skills" ? parentDir : null;
|
|
3115
3469
|
}
|
|
3116
3470
|
/**
|
|
@@ -3172,15 +3526,15 @@ var CustomCommandStore = class {
|
|
|
3172
3526
|
*/
|
|
3173
3527
|
async createCommandFile(name, isGlobal = false) {
|
|
3174
3528
|
const targetDir = isGlobal ? this.globalCommandsDirs[0] : this.projectCommandsDirs[0];
|
|
3175
|
-
const filePath =
|
|
3176
|
-
const fileExists = await
|
|
3529
|
+
const filePath = path7.join(targetDir, `${name}.md`);
|
|
3530
|
+
const fileExists = await fs6.access(filePath).then(
|
|
3177
3531
|
() => true,
|
|
3178
3532
|
() => false
|
|
3179
3533
|
);
|
|
3180
3534
|
if (fileExists) {
|
|
3181
3535
|
throw new Error(`Command file already exists: ${filePath}`);
|
|
3182
3536
|
}
|
|
3183
|
-
await
|
|
3537
|
+
await fs6.mkdir(targetDir, { recursive: true });
|
|
3184
3538
|
const template = `---
|
|
3185
3539
|
description: ${name} command
|
|
3186
3540
|
argument-hint: [args]
|
|
@@ -3195,7 +3549,7 @@ You can use:
|
|
|
3195
3549
|
- $1, $2, etc. for positional arguments
|
|
3196
3550
|
- @filename for file references
|
|
3197
3551
|
`;
|
|
3198
|
-
await
|
|
3552
|
+
await fs6.writeFile(filePath, template, "utf-8");
|
|
3199
3553
|
return filePath;
|
|
3200
3554
|
}
|
|
3201
3555
|
};
|
|
@@ -4427,7 +4781,7 @@ import { GoogleGenerativeAI } from "@google/generative-ai";
|
|
|
4427
4781
|
import OpenAI2 from "openai";
|
|
4428
4782
|
|
|
4429
4783
|
// ../../b4m-core/packages/services/dist/src/importHistoryService/index.js
|
|
4430
|
-
import
|
|
4784
|
+
import fs7, { unlinkSync as unlinkSync2 } from "fs";
|
|
4431
4785
|
import yauzl from "yauzl";
|
|
4432
4786
|
import axios3 from "axios";
|
|
4433
4787
|
|
|
@@ -6118,8 +6472,8 @@ async function processAndStoreImages(images, context) {
|
|
|
6118
6472
|
const buffer = await downloadImage(image);
|
|
6119
6473
|
const fileType = await fileTypeFromBuffer2(buffer);
|
|
6120
6474
|
const filename = `${uuidv45()}.${fileType?.ext}`;
|
|
6121
|
-
const
|
|
6122
|
-
return
|
|
6475
|
+
const path21 = await context.imageGenerateStorage.upload(buffer, filename, {});
|
|
6476
|
+
return path21;
|
|
6123
6477
|
}));
|
|
6124
6478
|
}
|
|
6125
6479
|
async function updateQuestAndReturnMarkdown(storedImageUrls, context) {
|
|
@@ -7445,8 +7799,8 @@ async function processAndStoreImage(imageUrl, context) {
|
|
|
7445
7799
|
const buffer = await downloadImage2(imageUrl);
|
|
7446
7800
|
const fileType = await fileTypeFromBuffer3(buffer);
|
|
7447
7801
|
const filename = `${uuidv46()}.${fileType?.ext}`;
|
|
7448
|
-
const
|
|
7449
|
-
return
|
|
7802
|
+
const path21 = await context.imageGenerateStorage.upload(buffer, filename, {});
|
|
7803
|
+
return path21;
|
|
7450
7804
|
}
|
|
7451
7805
|
async function generateFullMask(imageBuffer) {
|
|
7452
7806
|
const sharp = (await import("sharp")).default;
|
|
@@ -8064,7 +8418,7 @@ var blogDraftTool = {
|
|
|
8064
8418
|
outputFormat,
|
|
8065
8419
|
additionalInstructions: params.additionalInstructions
|
|
8066
8420
|
});
|
|
8067
|
-
const modelToUse = context.model ||
|
|
8421
|
+
const modelToUse = context.model || ChatModels.CLAUDE_4_6_SONNET;
|
|
8068
8422
|
logger2.info("Using model for blog draft:", { model: modelToUse });
|
|
8069
8423
|
let llmResponse = "";
|
|
8070
8424
|
await llm.complete(modelToUse, [
|
|
@@ -9387,33 +9741,33 @@ var planetVisibilityTool = {
|
|
|
9387
9741
|
};
|
|
9388
9742
|
|
|
9389
9743
|
// ../../b4m-core/packages/services/dist/src/llm/tools/implementation/fileRead/index.js
|
|
9390
|
-
import { promises as
|
|
9391
|
-
import { existsSync as
|
|
9392
|
-
import
|
|
9393
|
-
var
|
|
9744
|
+
import { promises as fs8 } from "fs";
|
|
9745
|
+
import { existsSync as existsSync5, statSync as statSync4 } from "fs";
|
|
9746
|
+
import path8 from "path";
|
|
9747
|
+
var MAX_FILE_SIZE3 = 10 * 1024 * 1024;
|
|
9394
9748
|
async function readFileContent(params) {
|
|
9395
9749
|
const { path: filePath, encoding = "utf-8", offset = 0, limit } = params;
|
|
9396
|
-
const normalizedPath =
|
|
9397
|
-
const resolvedPath =
|
|
9398
|
-
const cwd =
|
|
9750
|
+
const normalizedPath = path8.normalize(filePath);
|
|
9751
|
+
const resolvedPath = path8.resolve(process.cwd(), normalizedPath);
|
|
9752
|
+
const cwd = path8.resolve(process.cwd());
|
|
9399
9753
|
if (!resolvedPath.startsWith(cwd)) {
|
|
9400
9754
|
throw new Error(`Access denied: Cannot read files outside of current working directory`);
|
|
9401
9755
|
}
|
|
9402
|
-
if (!
|
|
9756
|
+
if (!existsSync5(resolvedPath)) {
|
|
9403
9757
|
throw new Error(`File not found: ${filePath}`);
|
|
9404
9758
|
}
|
|
9405
9759
|
const stats = statSync4(resolvedPath);
|
|
9406
9760
|
if (stats.isDirectory()) {
|
|
9407
9761
|
throw new Error(`Path is a directory, not a file: ${filePath}`);
|
|
9408
9762
|
}
|
|
9409
|
-
if (stats.size >
|
|
9410
|
-
throw new Error(`File too large: ${(stats.size / 1024 / 1024).toFixed(2)}MB (max ${
|
|
9763
|
+
if (stats.size > MAX_FILE_SIZE3) {
|
|
9764
|
+
throw new Error(`File too large: ${(stats.size / 1024 / 1024).toFixed(2)}MB (max ${MAX_FILE_SIZE3 / 1024 / 1024}MB)`);
|
|
9411
9765
|
}
|
|
9412
9766
|
const isBinary = await checkIfBinary(resolvedPath);
|
|
9413
9767
|
if (isBinary && encoding === "utf-8") {
|
|
9414
9768
|
throw new Error(`File appears to be binary. Use encoding 'base64' to read binary files, or specify a different encoding.`);
|
|
9415
9769
|
}
|
|
9416
|
-
const content = await
|
|
9770
|
+
const content = await fs8.readFile(resolvedPath, encoding);
|
|
9417
9771
|
if (typeof content === "string") {
|
|
9418
9772
|
const lines = content.split("\n");
|
|
9419
9773
|
const totalLines = lines.length;
|
|
@@ -9451,7 +9805,7 @@ ${content}`;
|
|
|
9451
9805
|
}
|
|
9452
9806
|
async function checkIfBinary(filePath) {
|
|
9453
9807
|
const buffer = Buffer.alloc(8192);
|
|
9454
|
-
const fd = await
|
|
9808
|
+
const fd = await fs8.open(filePath, "r");
|
|
9455
9809
|
try {
|
|
9456
9810
|
const { bytesRead } = await fd.read(buffer, 0, 8192, 0);
|
|
9457
9811
|
const chunk = buffer.slice(0, bytesRead);
|
|
@@ -9468,7 +9822,7 @@ var fileReadTool = {
|
|
|
9468
9822
|
context.logger.info("\u{1F4C4} FileRead: Reading file", { path: params.path });
|
|
9469
9823
|
try {
|
|
9470
9824
|
const content = await readFileContent(params);
|
|
9471
|
-
const stats = statSync4(
|
|
9825
|
+
const stats = statSync4(path8.resolve(process.cwd(), path8.normalize(params.path)));
|
|
9472
9826
|
context.logger.info("\u2705 FileRead: Success", {
|
|
9473
9827
|
path: params.path,
|
|
9474
9828
|
size: stats.size,
|
|
@@ -9512,25 +9866,25 @@ var fileReadTool = {
|
|
|
9512
9866
|
};
|
|
9513
9867
|
|
|
9514
9868
|
// ../../b4m-core/packages/services/dist/src/llm/tools/implementation/createFile/index.js
|
|
9515
|
-
import { promises as
|
|
9516
|
-
import { existsSync as
|
|
9517
|
-
import
|
|
9869
|
+
import { promises as fs9 } from "fs";
|
|
9870
|
+
import { existsSync as existsSync6 } from "fs";
|
|
9871
|
+
import path9 from "path";
|
|
9518
9872
|
async function createFile(params) {
|
|
9519
9873
|
const { path: filePath, content, createDirectories = true } = params;
|
|
9520
|
-
const normalizedPath =
|
|
9521
|
-
const resolvedPath =
|
|
9522
|
-
const cwd =
|
|
9874
|
+
const normalizedPath = path9.normalize(filePath);
|
|
9875
|
+
const resolvedPath = path9.resolve(process.cwd(), normalizedPath);
|
|
9876
|
+
const cwd = path9.resolve(process.cwd());
|
|
9523
9877
|
if (!resolvedPath.startsWith(cwd)) {
|
|
9524
9878
|
throw new Error(`Access denied: Cannot create files outside of current working directory`);
|
|
9525
9879
|
}
|
|
9526
|
-
const fileExists =
|
|
9880
|
+
const fileExists = existsSync6(resolvedPath);
|
|
9527
9881
|
const action = fileExists ? "overwritten" : "created";
|
|
9528
9882
|
if (createDirectories) {
|
|
9529
|
-
const dir =
|
|
9530
|
-
await
|
|
9883
|
+
const dir = path9.dirname(resolvedPath);
|
|
9884
|
+
await fs9.mkdir(dir, { recursive: true });
|
|
9531
9885
|
}
|
|
9532
|
-
await
|
|
9533
|
-
const stats = await
|
|
9886
|
+
await fs9.writeFile(resolvedPath, content, "utf-8");
|
|
9887
|
+
const stats = await fs9.stat(resolvedPath);
|
|
9534
9888
|
const lines = content.split("\n").length;
|
|
9535
9889
|
return `File ${action} successfully: ${filePath}
|
|
9536
9890
|
Size: ${stats.size} bytes
|
|
@@ -9541,7 +9895,7 @@ var createFileTool = {
|
|
|
9541
9895
|
implementation: (context) => ({
|
|
9542
9896
|
toolFn: async (value) => {
|
|
9543
9897
|
const params = value;
|
|
9544
|
-
const fileExists =
|
|
9898
|
+
const fileExists = existsSync6(path9.resolve(process.cwd(), path9.normalize(params.path)));
|
|
9545
9899
|
context.logger.info(`\u{1F4DD} CreateFile: ${fileExists ? "Overwriting" : "Creating"} file`, {
|
|
9546
9900
|
path: params.path,
|
|
9547
9901
|
size: params.content.length
|
|
@@ -9583,7 +9937,7 @@ var createFileTool = {
|
|
|
9583
9937
|
// ../../b4m-core/packages/services/dist/src/llm/tools/implementation/globFiles/index.js
|
|
9584
9938
|
import { glob } from "glob";
|
|
9585
9939
|
import { stat } from "fs/promises";
|
|
9586
|
-
import
|
|
9940
|
+
import path10 from "path";
|
|
9587
9941
|
var DEFAULT_IGNORE_PATTERNS = [
|
|
9588
9942
|
"**/node_modules/**",
|
|
9589
9943
|
"**/.git/**",
|
|
@@ -9599,7 +9953,7 @@ var DEFAULT_IGNORE_PATTERNS = [
|
|
|
9599
9953
|
async function findFiles(params) {
|
|
9600
9954
|
const { pattern, dir_path, case_sensitive = true, respect_git_ignore = true } = params;
|
|
9601
9955
|
const baseCwd = process.cwd();
|
|
9602
|
-
const targetDir = dir_path ?
|
|
9956
|
+
const targetDir = dir_path ? path10.resolve(baseCwd, path10.normalize(dir_path)) : baseCwd;
|
|
9603
9957
|
if (!targetDir.startsWith(baseCwd)) {
|
|
9604
9958
|
throw new Error(`Access denied: Cannot search outside of current working directory`);
|
|
9605
9959
|
}
|
|
@@ -9639,7 +9993,7 @@ async function findFiles(params) {
|
|
|
9639
9993
|
const summary = `Found ${filesWithStats.length} file(s)${truncated ? ` (showing first ${MAX_RESULTS})` : ""} matching: ${pattern}`;
|
|
9640
9994
|
const dirInfo = dir_path ? `
|
|
9641
9995
|
Directory: ${dir_path}` : "";
|
|
9642
|
-
const filesList = results.map((file) =>
|
|
9996
|
+
const filesList = results.map((file) => path10.relative(baseCwd, file.path)).join("\n");
|
|
9643
9997
|
return `${summary}${dirInfo}
|
|
9644
9998
|
|
|
9645
9999
|
${filesList}`;
|
|
@@ -9693,27 +10047,48 @@ var globFilesTool = {
|
|
|
9693
10047
|
|
|
9694
10048
|
// ../../b4m-core/packages/services/dist/src/llm/tools/implementation/grepSearch/index.js
|
|
9695
10049
|
import { stat as stat2 } from "fs/promises";
|
|
9696
|
-
import
|
|
9697
|
-
import
|
|
10050
|
+
import { existsSync as existsSync7 } from "fs";
|
|
10051
|
+
import path11 from "path";
|
|
10052
|
+
import { execFile, execFileSync as execFileSync2 } from "child_process";
|
|
9698
10053
|
import { promisify } from "util";
|
|
9699
10054
|
import { createRequire } from "module";
|
|
9700
10055
|
var execFileAsync = promisify(execFile);
|
|
9701
10056
|
var require2 = createRequire(import.meta.url);
|
|
10057
|
+
var cachedRgPath = null;
|
|
9702
10058
|
function getRipgrepPath() {
|
|
10059
|
+
if (cachedRgPath)
|
|
10060
|
+
return cachedRgPath;
|
|
9703
10061
|
try {
|
|
9704
10062
|
const ripgrepPath = require2.resolve("@vscode/ripgrep");
|
|
9705
|
-
const ripgrepDir =
|
|
9706
|
-
const rgBinary =
|
|
10063
|
+
const ripgrepDir = path11.dirname(ripgrepPath);
|
|
10064
|
+
const rgBinary = path11.join(ripgrepDir, "..", "bin", "rg" + (process.platform === "win32" ? ".exe" : ""));
|
|
10065
|
+
if (!existsSync7(rgBinary)) {
|
|
10066
|
+
const postinstallScript = path11.join(ripgrepDir, "..", "lib", "postinstall.js");
|
|
10067
|
+
if (existsSync7(postinstallScript)) {
|
|
10068
|
+
execFileSync2(process.execPath, [postinstallScript], {
|
|
10069
|
+
cwd: path11.join(ripgrepDir, ".."),
|
|
10070
|
+
stdio: "pipe",
|
|
10071
|
+
timeout: 3e4
|
|
10072
|
+
});
|
|
10073
|
+
}
|
|
10074
|
+
if (!existsSync7(rgBinary)) {
|
|
10075
|
+
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`);
|
|
10076
|
+
}
|
|
10077
|
+
}
|
|
10078
|
+
cachedRgPath = rgBinary;
|
|
9707
10079
|
return rgBinary;
|
|
9708
10080
|
} catch (error) {
|
|
10081
|
+
if (error instanceof Error && error.message.includes("ripgrep binary not found")) {
|
|
10082
|
+
throw error;
|
|
10083
|
+
}
|
|
9709
10084
|
throw new Error("ripgrep is not available. Please install @vscode/ripgrep: pnpm add @vscode/ripgrep --filter @bike4mind/services");
|
|
9710
10085
|
}
|
|
9711
10086
|
}
|
|
9712
10087
|
function isPathWithinWorkspace(targetPath, baseCwd) {
|
|
9713
|
-
const resolvedTarget =
|
|
9714
|
-
const resolvedBase =
|
|
9715
|
-
const relativePath =
|
|
9716
|
-
return !relativePath.startsWith("..") && !
|
|
10088
|
+
const resolvedTarget = path11.resolve(targetPath);
|
|
10089
|
+
const resolvedBase = path11.resolve(baseCwd);
|
|
10090
|
+
const relativePath = path11.relative(resolvedBase, resolvedTarget);
|
|
10091
|
+
return !relativePath.startsWith("..") && !path11.isAbsolute(relativePath);
|
|
9717
10092
|
}
|
|
9718
10093
|
function convertGlobToRipgrepGlobs(globPattern) {
|
|
9719
10094
|
if (!globPattern || globPattern === "**/*") {
|
|
@@ -9729,7 +10104,7 @@ function convertGlobToRipgrepGlobs(globPattern) {
|
|
|
9729
10104
|
async function searchFiles2(params) {
|
|
9730
10105
|
const { pattern, dir_path, include } = params;
|
|
9731
10106
|
const baseCwd = process.cwd();
|
|
9732
|
-
const targetDir = dir_path ?
|
|
10107
|
+
const targetDir = dir_path ? path11.resolve(baseCwd, dir_path) : baseCwd;
|
|
9733
10108
|
if (!isPathWithinWorkspace(targetDir, baseCwd)) {
|
|
9734
10109
|
throw new Error(`Path validation failed: "${dir_path}" resolves outside the allowed workspace directory`);
|
|
9735
10110
|
}
|
|
@@ -9791,7 +10166,7 @@ async function searchFiles2(params) {
|
|
|
9791
10166
|
if (item.type === "match") {
|
|
9792
10167
|
const match = item;
|
|
9793
10168
|
allMatches.push({
|
|
9794
|
-
filePath:
|
|
10169
|
+
filePath: path11.relative(targetDir, match.data.path.text) || path11.basename(match.data.path.text),
|
|
9795
10170
|
lineNumber: match.data.line_number,
|
|
9796
10171
|
line: match.data.lines.text.trimEnd()
|
|
9797
10172
|
// Remove trailing newline
|
|
@@ -9884,18 +10259,18 @@ var grepSearchTool = {
|
|
|
9884
10259
|
};
|
|
9885
10260
|
|
|
9886
10261
|
// ../../b4m-core/packages/services/dist/src/llm/tools/implementation/deleteFile/index.js
|
|
9887
|
-
import { promises as
|
|
9888
|
-
import { existsSync as
|
|
9889
|
-
import
|
|
10262
|
+
import { promises as fs10 } from "fs";
|
|
10263
|
+
import { existsSync as existsSync8, statSync as statSync5 } from "fs";
|
|
10264
|
+
import path12 from "path";
|
|
9890
10265
|
async function deleteFile(params) {
|
|
9891
10266
|
const { path: filePath, recursive = false } = params;
|
|
9892
|
-
const normalizedPath =
|
|
9893
|
-
const resolvedPath =
|
|
9894
|
-
const cwd =
|
|
10267
|
+
const normalizedPath = path12.normalize(filePath);
|
|
10268
|
+
const resolvedPath = path12.resolve(process.cwd(), normalizedPath);
|
|
10269
|
+
const cwd = path12.resolve(process.cwd());
|
|
9895
10270
|
if (!resolvedPath.startsWith(cwd)) {
|
|
9896
10271
|
throw new Error(`Access denied: Cannot delete files outside of current working directory`);
|
|
9897
10272
|
}
|
|
9898
|
-
if (!
|
|
10273
|
+
if (!existsSync8(resolvedPath)) {
|
|
9899
10274
|
throw new Error(`File or directory not found: ${filePath}`);
|
|
9900
10275
|
}
|
|
9901
10276
|
const stats = statSync5(resolvedPath);
|
|
@@ -9905,10 +10280,10 @@ async function deleteFile(params) {
|
|
|
9905
10280
|
throw new Error(`Path is a directory: ${filePath}. Use recursive=true to delete directories and their contents.`);
|
|
9906
10281
|
}
|
|
9907
10282
|
if (isDirectory) {
|
|
9908
|
-
await
|
|
10283
|
+
await fs10.rm(resolvedPath, { recursive: true, force: true });
|
|
9909
10284
|
return `Directory deleted successfully: ${filePath}`;
|
|
9910
10285
|
} else {
|
|
9911
|
-
await
|
|
10286
|
+
await fs10.unlink(resolvedPath);
|
|
9912
10287
|
return `File deleted successfully: ${filePath}
|
|
9913
10288
|
Size: ${size} bytes`;
|
|
9914
10289
|
}
|
|
@@ -9918,8 +10293,8 @@ var deleteFileTool = {
|
|
|
9918
10293
|
implementation: (context) => ({
|
|
9919
10294
|
toolFn: async (value) => {
|
|
9920
10295
|
const params = value;
|
|
9921
|
-
const resolvedPath =
|
|
9922
|
-
const isDirectory =
|
|
10296
|
+
const resolvedPath = path12.resolve(process.cwd(), path12.normalize(params.path));
|
|
10297
|
+
const isDirectory = existsSync8(resolvedPath) && statSync5(resolvedPath).isDirectory();
|
|
9923
10298
|
context.logger.info(`\u{1F5D1}\uFE0F DeleteFile: Deleting ${isDirectory ? "directory" : "file"}`, {
|
|
9924
10299
|
path: params.path,
|
|
9925
10300
|
recursive: params.recursive
|
|
@@ -11496,6 +11871,7 @@ var solverMetadata = {
|
|
|
11496
11871
|
requires: "Backend compute",
|
|
11497
11872
|
available: false,
|
|
11498
11873
|
tagline: "Quantum-inspired classical simulation",
|
|
11874
|
+
externalUrl: "https://q.bike4mind.com/jobs/e499f34a-d40e-4cfb-98e5-47e0a2091f3d",
|
|
11499
11875
|
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.",
|
|
11500
11876
|
howItWorks: [
|
|
11501
11877
|
"Encode scheduling problem as QUBO matrix",
|
|
@@ -11541,6 +11917,7 @@ var solverMetadata = {
|
|
|
11541
11917
|
requires: "IonQ API key + credits",
|
|
11542
11918
|
available: false,
|
|
11543
11919
|
tagline: "Real quantum hardware optimization",
|
|
11920
|
+
externalUrl: "https://cloud.ionq.com/jobs/019c07ff-205a-76d0-9f83-24098ea09589",
|
|
11544
11921
|
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.",
|
|
11545
11922
|
howItWorks: [
|
|
11546
11923
|
"Encode scheduling problem as QUBO matrix",
|
|
@@ -12598,7 +12975,7 @@ var navigateViewTool = {
|
|
|
12598
12975
|
|
|
12599
12976
|
// ../../b4m-core/packages/services/dist/src/llm/tools/implementation/bashExecute/index.js
|
|
12600
12977
|
import { spawn } from "child_process";
|
|
12601
|
-
import
|
|
12978
|
+
import path13 from "path";
|
|
12602
12979
|
var DEFAULT_TIMEOUT_MS = 6e4;
|
|
12603
12980
|
var MAX_OUTPUT_SIZE = 100 * 1024;
|
|
12604
12981
|
var DANGEROUS_PATTERNS = [
|
|
@@ -12782,7 +13159,7 @@ async function executeBashCommand(params) {
|
|
|
12782
13159
|
};
|
|
12783
13160
|
}
|
|
12784
13161
|
const baseCwd = process.cwd();
|
|
12785
|
-
const targetCwd = relativeCwd ?
|
|
13162
|
+
const targetCwd = relativeCwd ? path13.resolve(baseCwd, relativeCwd) : baseCwd;
|
|
12786
13163
|
const effectiveTimeout = Math.min(timeout, 5 * 60 * 1e3);
|
|
12787
13164
|
return new Promise((resolve3) => {
|
|
12788
13165
|
let stdout = "";
|
|
@@ -13785,9 +14162,9 @@ function parseQuery(query) {
|
|
|
13785
14162
|
}
|
|
13786
14163
|
|
|
13787
14164
|
// ../../b4m-core/packages/services/dist/src/llm/tools/implementation/editLocalFile/index.js
|
|
13788
|
-
import { promises as
|
|
13789
|
-
import { existsSync as
|
|
13790
|
-
import
|
|
14165
|
+
import { promises as fs11 } from "fs";
|
|
14166
|
+
import { existsSync as existsSync9 } from "fs";
|
|
14167
|
+
import path14 from "path";
|
|
13791
14168
|
import { diffLines as diffLines3 } from "diff";
|
|
13792
14169
|
function generateDiff(original, modified) {
|
|
13793
14170
|
const differences = diffLines3(original, modified);
|
|
@@ -13811,16 +14188,16 @@ function generateDiff(original, modified) {
|
|
|
13811
14188
|
}
|
|
13812
14189
|
async function editLocalFile(params) {
|
|
13813
14190
|
const { path: filePath, old_string, new_string } = params;
|
|
13814
|
-
const normalizedPath =
|
|
13815
|
-
const resolvedPath =
|
|
13816
|
-
const cwd =
|
|
14191
|
+
const normalizedPath = path14.normalize(filePath);
|
|
14192
|
+
const resolvedPath = path14.resolve(process.cwd(), normalizedPath);
|
|
14193
|
+
const cwd = path14.resolve(process.cwd());
|
|
13817
14194
|
if (!resolvedPath.startsWith(cwd)) {
|
|
13818
14195
|
throw new Error(`Access denied: Cannot edit files outside of current working directory`);
|
|
13819
14196
|
}
|
|
13820
|
-
if (!
|
|
14197
|
+
if (!existsSync9(resolvedPath)) {
|
|
13821
14198
|
throw new Error(`File not found: ${filePath}`);
|
|
13822
14199
|
}
|
|
13823
|
-
const currentContent = await
|
|
14200
|
+
const currentContent = await fs11.readFile(resolvedPath, "utf-8");
|
|
13824
14201
|
if (!currentContent.includes(old_string)) {
|
|
13825
14202
|
const preview = old_string.length > 100 ? old_string.substring(0, 100) + "..." : old_string;
|
|
13826
14203
|
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}"`);
|
|
@@ -13830,7 +14207,7 @@ async function editLocalFile(params) {
|
|
|
13830
14207
|
throw new Error(`Found ${occurrences} occurrences of the string to replace. Please provide a more specific old_string that matches exactly one location.`);
|
|
13831
14208
|
}
|
|
13832
14209
|
const newContent = currentContent.replace(old_string, new_string);
|
|
13833
|
-
await
|
|
14210
|
+
await fs11.writeFile(resolvedPath, newContent, "utf-8");
|
|
13834
14211
|
const diffResult = generateDiff(old_string, new_string);
|
|
13835
14212
|
return `File edited successfully: ${filePath}
|
|
13836
14213
|
Changes: +${diffResult.additions} lines, -${diffResult.deletions} lines
|
|
@@ -14390,7 +14767,8 @@ var SUBAGENT_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
|
14390
14767
|
var CodeReviewAgent = (config) => ({
|
|
14391
14768
|
name: "code_review",
|
|
14392
14769
|
description: "Code review specialist for analyzing code quality, bugs, and improvements",
|
|
14393
|
-
model: config?.model ??
|
|
14770
|
+
model: config?.model ?? ChatModels.CLAUDE_4_6_SONNET,
|
|
14771
|
+
fallbackModels: [ChatModels.GPT4_1, ChatModels.GPT4_1_MINI],
|
|
14394
14772
|
defaultThoroughness: config?.defaultThoroughness ?? "medium",
|
|
14395
14773
|
maxIterations: { quick: 3, medium: 8, very_thorough: 15 },
|
|
14396
14774
|
deniedTools: ["image_generation", "edit_image", "delegate_to_agent", ...config?.extraDeniedTools ?? []],
|
|
@@ -14430,7 +14808,8 @@ Focus on actionable, specific feedback referencing exact code locations. Your re
|
|
|
14430
14808
|
var ProjectManagerAgent = (config) => ({
|
|
14431
14809
|
name: "project_manager",
|
|
14432
14810
|
description: "Project management via Jira and Confluence (create issues, search, update status, write docs). ALWAYS delegate Jira/Confluence requests to this agent \u2014 you do not have direct access to these tools",
|
|
14433
|
-
model: config?.model ??
|
|
14811
|
+
model: config?.model ?? ChatModels.CLAUDE_4_6_SONNET,
|
|
14812
|
+
fallbackModels: [ChatModels.GPT4_1, ChatModels.GPT4_1_MINI],
|
|
14434
14813
|
defaultThoroughness: config?.defaultThoroughness ?? "medium",
|
|
14435
14814
|
maxIterations: { quick: 3, medium: 8, very_thorough: 15 },
|
|
14436
14815
|
allowedTools: [...config?.extraAllowedTools ?? []],
|
|
@@ -14471,7 +14850,8 @@ Be precise with issue keys and project names. Your results will be used by the m
|
|
|
14471
14850
|
var GithubManagerAgent = (config) => ({
|
|
14472
14851
|
name: "github_manager",
|
|
14473
14852
|
description: "GitHub operations (issues, pull requests, code search, branches, workflows, reviews). ALWAYS delegate GitHub requests to this agent \u2014 you do not have direct access to these tools",
|
|
14474
|
-
model: config?.model ??
|
|
14853
|
+
model: config?.model ?? ChatModels.CLAUDE_4_6_SONNET,
|
|
14854
|
+
fallbackModels: [ChatModels.GPT4_1, ChatModels.GPT4_1_MINI],
|
|
14475
14855
|
defaultThoroughness: config?.defaultThoroughness ?? "medium",
|
|
14476
14856
|
maxIterations: { quick: 3, medium: 8, very_thorough: 15 },
|
|
14477
14857
|
allowedTools: [...config?.extraAllowedTools ?? []],
|
|
@@ -14479,12 +14859,27 @@ var GithubManagerAgent = (config) => ({
|
|
|
14479
14859
|
exclusiveMcpServers: ["github"],
|
|
14480
14860
|
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.
|
|
14481
14861
|
|
|
14482
|
-
## First Step: Resolve Repository Context
|
|
14483
|
-
|
|
14862
|
+
## First Step: ALWAYS Resolve Repository Context Automatically
|
|
14863
|
+
Before doing ANYTHING else, call the \`github__current_user\` tool. The response includes:
|
|
14484
14864
|
- **selected_repositories**: The list of repositories the user has enabled for AI access (in \`owner/repo\` format)
|
|
14485
14865
|
- **user**: The authenticated user's profile (login, name, etc.)
|
|
14486
14866
|
|
|
14487
|
-
|
|
14867
|
+
### Repository Resolution Rules (CRITICAL)
|
|
14868
|
+
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:
|
|
14869
|
+
|
|
14870
|
+
1. **Exact match**: If the user says a repo name that exactly matches a repo name in selected_repositories, use it immediately.
|
|
14871
|
+
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).
|
|
14872
|
+
3. **Single repo shortcut**: If only one repository is selected, ALWAYS default to it \u2014 no questions asked.
|
|
14873
|
+
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.
|
|
14874
|
+
5. **No match**: If nothing in selected_repositories matches, tell the user which repos are available and ask them to clarify.
|
|
14875
|
+
|
|
14876
|
+
**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.
|
|
14877
|
+
|
|
14878
|
+
### Defaults for Ambiguous Requests
|
|
14879
|
+
- **Timezone**: Default to UTC unless the user specifies otherwise.
|
|
14880
|
+
- **PR/Issue state**: Default to \`open\` unless the user specifies otherwise.
|
|
14881
|
+
- **Scope**: All matching repos unless the user narrows it down.
|
|
14882
|
+
- **When in doubt, act**: Prefer making reasonable assumptions and proceeding over asking clarifying questions. You can always note your assumptions in the response.
|
|
14488
14883
|
|
|
14489
14884
|
## Capabilities
|
|
14490
14885
|
|
|
@@ -14525,7 +14920,7 @@ Use this to resolve the owner and repo name when the user does not specify them.
|
|
|
14525
14920
|
## Output Format
|
|
14526
14921
|
Provide a clear summary of actions taken:
|
|
14527
14922
|
1. What was done (created, searched, merged, etc.)
|
|
14528
|
-
2.
|
|
14923
|
+
2. Always provide links to relevant items (e.g., owner/repo#123, PR URLs)
|
|
14529
14924
|
3. Any issues or warnings encountered
|
|
14530
14925
|
|
|
14531
14926
|
Be precise with repository names, issue numbers, and PR numbers. Your results will be used by the main agent.`
|
|
@@ -15220,10 +15615,10 @@ var ToolErrorType;
|
|
|
15220
15615
|
// src/utils/diffPreview.ts
|
|
15221
15616
|
import * as Diff from "diff";
|
|
15222
15617
|
import { readFile } from "fs/promises";
|
|
15223
|
-
import { existsSync as
|
|
15618
|
+
import { existsSync as existsSync10 } from "fs";
|
|
15224
15619
|
async function generateFileDiffPreview(args) {
|
|
15225
15620
|
try {
|
|
15226
|
-
if (!
|
|
15621
|
+
if (!existsSync10(args.path)) {
|
|
15227
15622
|
const lines2 = args.content.split("\n");
|
|
15228
15623
|
const preview = lines2.slice(0, 20).join("\n");
|
|
15229
15624
|
const hasMore = lines2.length > 20;
|
|
@@ -15261,10 +15656,10 @@ ${diffLines4.join("\n")}`;
|
|
|
15261
15656
|
}
|
|
15262
15657
|
async function generateFileDeletePreview(args) {
|
|
15263
15658
|
try {
|
|
15264
|
-
if (!
|
|
15659
|
+
if (!existsSync10(args.path)) {
|
|
15265
15660
|
return `[File does not exist: ${args.path}]`;
|
|
15266
15661
|
}
|
|
15267
|
-
const stats = await import("fs/promises").then((
|
|
15662
|
+
const stats = await import("fs/promises").then((fs15) => fs15.stat(args.path));
|
|
15268
15663
|
return `[File will be deleted]
|
|
15269
15664
|
|
|
15270
15665
|
Path: ${args.path}
|
|
@@ -15555,33 +15950,34 @@ var AgentFrontmatterSchema = z147.object({
|
|
|
15555
15950
|
hooks: AgentHooksSchema
|
|
15556
15951
|
});
|
|
15557
15952
|
var DEFAULT_MAX_ITERATIONS = {
|
|
15558
|
-
quick:
|
|
15559
|
-
medium:
|
|
15560
|
-
very_thorough:
|
|
15953
|
+
quick: 2,
|
|
15954
|
+
medium: 5,
|
|
15955
|
+
very_thorough: 10
|
|
15561
15956
|
};
|
|
15562
|
-
var DEFAULT_AGENT_MODEL = "claude-
|
|
15957
|
+
var DEFAULT_AGENT_MODEL = "claude-3-5-haiku-20241022";
|
|
15563
15958
|
var DEFAULT_THOROUGHNESS = "medium";
|
|
15564
15959
|
|
|
15565
15960
|
// src/utils/toolsAdapter.ts
|
|
15961
|
+
import path15 from "path";
|
|
15566
15962
|
var NoOpStorage = class extends BaseStorage {
|
|
15567
15963
|
async upload(input, destination, options) {
|
|
15568
15964
|
return `/tmp/${destination}`;
|
|
15569
15965
|
}
|
|
15570
|
-
async download(
|
|
15966
|
+
async download(path21) {
|
|
15571
15967
|
throw new Error("Download not supported in CLI");
|
|
15572
15968
|
}
|
|
15573
|
-
async delete(
|
|
15969
|
+
async delete(path21) {
|
|
15574
15970
|
}
|
|
15575
|
-
async getSignedUrl(
|
|
15576
|
-
return `/tmp/${
|
|
15971
|
+
async getSignedUrl(path21) {
|
|
15972
|
+
return `/tmp/${path21}`;
|
|
15577
15973
|
}
|
|
15578
|
-
getPublicUrl(
|
|
15579
|
-
return `/tmp/${
|
|
15974
|
+
getPublicUrl(path21) {
|
|
15975
|
+
return `/tmp/${path21}`;
|
|
15580
15976
|
}
|
|
15581
|
-
async getPreview(
|
|
15582
|
-
return `/tmp/${
|
|
15977
|
+
async getPreview(path21) {
|
|
15978
|
+
return `/tmp/${path21}`;
|
|
15583
15979
|
}
|
|
15584
|
-
async getMetadata(
|
|
15980
|
+
async getMetadata(path21) {
|
|
15585
15981
|
return { size: 0, contentType: "application/octet-stream" };
|
|
15586
15982
|
}
|
|
15587
15983
|
};
|
|
@@ -15754,6 +16150,27 @@ function wrapToolWithHooks(tool, hooks, hookContext) {
|
|
|
15754
16150
|
}
|
|
15755
16151
|
};
|
|
15756
16152
|
}
|
|
16153
|
+
var CHECKPOINT_TOOLS = /* @__PURE__ */ new Set(["create_file", "edit_local_file", "delete_file"]);
|
|
16154
|
+
function wrapToolWithCheckpointing(tool, checkpointStore) {
|
|
16155
|
+
if (!checkpointStore || !CHECKPOINT_TOOLS.has(tool.toolSchema.name)) {
|
|
16156
|
+
return tool;
|
|
16157
|
+
}
|
|
16158
|
+
const originalFn = tool.toolFn;
|
|
16159
|
+
const toolName = tool.toolSchema.name;
|
|
16160
|
+
return {
|
|
16161
|
+
...tool,
|
|
16162
|
+
toolFn: async (args) => {
|
|
16163
|
+
const filePath = args?.path;
|
|
16164
|
+
if (filePath) {
|
|
16165
|
+
try {
|
|
16166
|
+
await checkpointStore.createCheckpoint(toolName, [filePath], `before-${toolName}-${path15.basename(filePath)}`);
|
|
16167
|
+
} catch {
|
|
16168
|
+
}
|
|
16169
|
+
}
|
|
16170
|
+
return originalFn(args);
|
|
16171
|
+
}
|
|
16172
|
+
};
|
|
16173
|
+
}
|
|
15757
16174
|
var TOOL_NAME_MAPPING = {
|
|
15758
16175
|
// Claude Code -> B4M
|
|
15759
16176
|
read: "file_read",
|
|
@@ -15778,7 +16195,7 @@ function normalizeToolName(toolName) {
|
|
|
15778
16195
|
}
|
|
15779
16196
|
return TOOL_NAME_MAPPING[toolName] || toolName;
|
|
15780
16197
|
}
|
|
15781
|
-
function generateCliTools(userId, llm, model, permissionManager, showPermissionPrompt, agentContext, configStore, apiClient, toolFilter) {
|
|
16198
|
+
function generateCliTools(userId, llm, model, permissionManager, showPermissionPrompt, agentContext, configStore, apiClient, toolFilter, checkpointStore) {
|
|
15782
16199
|
const logger2 = new CliLogger();
|
|
15783
16200
|
const storage = new NoOpStorage();
|
|
15784
16201
|
const user = {
|
|
@@ -15837,9 +16254,17 @@ function generateCliTools(userId, llm, model, permissionManager, showPermissionP
|
|
|
15837
16254
|
// imageProcessorLambdaName (not needed for CLI)
|
|
15838
16255
|
tools_to_generate
|
|
15839
16256
|
);
|
|
15840
|
-
let tools = Object.entries(toolsMap).map(
|
|
15841
|
-
|
|
15842
|
-
|
|
16257
|
+
let tools = Object.entries(toolsMap).map(([_, tool]) => {
|
|
16258
|
+
const permissionWrapped = wrapToolWithPermission(
|
|
16259
|
+
tool,
|
|
16260
|
+
permissionManager,
|
|
16261
|
+
showPermissionPrompt,
|
|
16262
|
+
agentContext,
|
|
16263
|
+
configStore,
|
|
16264
|
+
apiClient
|
|
16265
|
+
);
|
|
16266
|
+
return wrapToolWithCheckpointing(permissionWrapped, checkpointStore ?? null);
|
|
16267
|
+
});
|
|
15843
16268
|
if (toolFilter) {
|
|
15844
16269
|
const { allowedTools, deniedTools } = toolFilter;
|
|
15845
16270
|
const normalizedAllowed = allowedTools?.map(normalizeToolName);
|
|
@@ -16017,8 +16442,8 @@ function getEnvironmentName(configApiConfig) {
|
|
|
16017
16442
|
}
|
|
16018
16443
|
|
|
16019
16444
|
// src/utils/contextLoader.ts
|
|
16020
|
-
import * as
|
|
16021
|
-
import * as
|
|
16445
|
+
import * as fs12 from "fs";
|
|
16446
|
+
import * as path16 from "path";
|
|
16022
16447
|
import { homedir as homedir3 } from "os";
|
|
16023
16448
|
var CONTEXT_FILE_SIZE_LIMIT = 100 * 1024;
|
|
16024
16449
|
var PROJECT_CONTEXT_FILES = [
|
|
@@ -16039,9 +16464,9 @@ function formatFileSize2(bytes) {
|
|
|
16039
16464
|
return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
|
|
16040
16465
|
}
|
|
16041
16466
|
function tryReadContextFile(dir, filename, source) {
|
|
16042
|
-
const filePath =
|
|
16467
|
+
const filePath = path16.join(dir, filename);
|
|
16043
16468
|
try {
|
|
16044
|
-
const stats =
|
|
16469
|
+
const stats = fs12.lstatSync(filePath);
|
|
16045
16470
|
if (stats.isDirectory()) {
|
|
16046
16471
|
return null;
|
|
16047
16472
|
}
|
|
@@ -16055,7 +16480,7 @@ function tryReadContextFile(dir, filename, source) {
|
|
|
16055
16480
|
error: `${source === "global" ? "Global" : "Project"} ${filename} exceeds 100KB limit (${formatFileSize2(stats.size)})`
|
|
16056
16481
|
};
|
|
16057
16482
|
}
|
|
16058
|
-
const content =
|
|
16483
|
+
const content = fs12.readFileSync(filePath, "utf-8");
|
|
16059
16484
|
return {
|
|
16060
16485
|
filename,
|
|
16061
16486
|
content,
|
|
@@ -16107,7 +16532,7 @@ ${project.content}`;
|
|
|
16107
16532
|
}
|
|
16108
16533
|
async function loadContextFiles(projectDir) {
|
|
16109
16534
|
const errors = [];
|
|
16110
|
-
const globalDir =
|
|
16535
|
+
const globalDir = path16.join(homedir3(), ".bike4mind");
|
|
16111
16536
|
const projectDirectory = projectDir || process.cwd();
|
|
16112
16537
|
const [globalResult, projectResult] = await Promise.all([
|
|
16113
16538
|
Promise.resolve(findContextFile(globalDir, GLOBAL_CONTEXT_FILES, "global")),
|
|
@@ -16378,8 +16803,8 @@ function substituteArguments(template, args) {
|
|
|
16378
16803
|
// ../../b4m-core/packages/mcp/dist/src/client.js
|
|
16379
16804
|
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
16380
16805
|
import { Client as Client2 } from "@modelcontextprotocol/sdk/client/index.js";
|
|
16381
|
-
import
|
|
16382
|
-
import { existsSync as
|
|
16806
|
+
import path17 from "path";
|
|
16807
|
+
import { existsSync as existsSync11, readdirSync as readdirSync3 } from "fs";
|
|
16383
16808
|
var MCPClient = class {
|
|
16384
16809
|
// Note: This class handles MCP server communication with repository filtering
|
|
16385
16810
|
mcp;
|
|
@@ -16420,18 +16845,18 @@ var MCPClient = class {
|
|
|
16420
16845
|
const root = process.env.INIT_CWD || process.cwd();
|
|
16421
16846
|
const candidatePaths = [
|
|
16422
16847
|
// When running from SST Lambda with node_modules structure (copyFiles)
|
|
16423
|
-
|
|
16848
|
+
path17.join(root, `node_modules/@bike4mind/mcp/dist/src/${this.serverName}/index.js`),
|
|
16424
16849
|
// When running from SST Lambda deployed environment (/var/task)
|
|
16425
|
-
|
|
16850
|
+
path17.join(root, `b4m-core/packages/mcp/dist/src/${this.serverName}/index.js`),
|
|
16426
16851
|
// When running from SST Lambda (.sst/artifacts/mcpHandler-dev), navigate to monorepo root (3 levels up)
|
|
16427
|
-
|
|
16852
|
+
path17.join(root, `../../../b4m-core/packages/mcp/dist/src/${this.serverName}/index.js`),
|
|
16428
16853
|
// When running from packages/client (Next.js app), navigate to monorepo root (2 levels up)
|
|
16429
|
-
|
|
16854
|
+
path17.join(root, `../../b4m-core/packages/mcp/dist/src/${this.serverName}/index.js`),
|
|
16430
16855
|
// Original paths (backward compatibility)
|
|
16431
|
-
|
|
16432
|
-
|
|
16856
|
+
path17.join(root, `/b4m-core/packages/mcp/dist/src/${this.serverName}/index.js`),
|
|
16857
|
+
path17.join(root, "core", "mcp", "servers", this.serverName, "dist", "index.js")
|
|
16433
16858
|
];
|
|
16434
|
-
const serverScriptPath = candidatePaths.find((p) =>
|
|
16859
|
+
const serverScriptPath = candidatePaths.find((p) => existsSync11(p));
|
|
16435
16860
|
if (!serverScriptPath) {
|
|
16436
16861
|
const getDirectories = (source) => readdirSync3(source, { withFileTypes: true }).filter((dirent) => dirent.isDirectory()).map((dirent) => dirent.name);
|
|
16437
16862
|
console.error(`[MCP] Server script not found. Tried paths:`, candidatePaths);
|
|
@@ -17361,6 +17786,7 @@ var WebSocketLlmBackend = class {
|
|
|
17361
17786
|
settled = true;
|
|
17362
17787
|
this.wsManager.offRequest(requestId);
|
|
17363
17788
|
this.wsManager.offDisconnect(onDisconnect);
|
|
17789
|
+
options.abortSignal?.removeEventListener("abort", abortHandler);
|
|
17364
17790
|
action();
|
|
17365
17791
|
};
|
|
17366
17792
|
const settleResolve = () => settle(() => resolve3());
|
|
@@ -17370,19 +17796,16 @@ var WebSocketLlmBackend = class {
|
|
|
17370
17796
|
settleReject(new Error("WebSocket connection lost during completion"));
|
|
17371
17797
|
};
|
|
17372
17798
|
this.wsManager.onDisconnect(onDisconnect);
|
|
17799
|
+
const abortHandler = () => {
|
|
17800
|
+
logger.debug("[WebSocketLlmBackend] Abort signal received");
|
|
17801
|
+
settleResolve();
|
|
17802
|
+
};
|
|
17373
17803
|
if (options.abortSignal) {
|
|
17374
17804
|
if (options.abortSignal.aborted) {
|
|
17375
17805
|
settleResolve();
|
|
17376
17806
|
return;
|
|
17377
17807
|
}
|
|
17378
|
-
options.abortSignal.addEventListener(
|
|
17379
|
-
"abort",
|
|
17380
|
-
() => {
|
|
17381
|
-
logger.debug("[WebSocketLlmBackend] Abort signal received");
|
|
17382
|
-
settleResolve();
|
|
17383
|
-
},
|
|
17384
|
-
{ once: true }
|
|
17385
|
-
);
|
|
17808
|
+
options.abortSignal.addEventListener("abort", abortHandler, { once: true });
|
|
17386
17809
|
}
|
|
17387
17810
|
const updateUsage = (usage) => {
|
|
17388
17811
|
if (usage) {
|
|
@@ -17720,27 +18143,35 @@ var WebSocketToolExecutor = class {
|
|
|
17720
18143
|
const requestId = uuidv412();
|
|
17721
18144
|
return new Promise((resolve3, reject) => {
|
|
17722
18145
|
let settled = false;
|
|
18146
|
+
let timeoutTimer;
|
|
17723
18147
|
const settle = (action) => {
|
|
17724
18148
|
if (settled) return;
|
|
17725
18149
|
settled = true;
|
|
18150
|
+
clearTimeout(timeoutTimer);
|
|
17726
18151
|
this.wsManager.offRequest(requestId);
|
|
17727
18152
|
this.wsManager.offDisconnect(onDisconnect);
|
|
18153
|
+
abortSignal?.removeEventListener("abort", abortHandler);
|
|
17728
18154
|
action();
|
|
17729
18155
|
};
|
|
17730
18156
|
const settleResolve = (result) => settle(() => resolve3(result));
|
|
17731
18157
|
const settleReject = (err) => settle(() => reject(err));
|
|
18158
|
+
const TOOL_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
18159
|
+
timeoutTimer = setTimeout(() => {
|
|
18160
|
+
settleReject(new Error(`Tool execution timed out after ${TOOL_TIMEOUT_MS / 1e3}s`));
|
|
18161
|
+
}, TOOL_TIMEOUT_MS);
|
|
17732
18162
|
const onDisconnect = () => {
|
|
17733
18163
|
settleReject(new Error("WebSocket connection lost during tool execution"));
|
|
17734
18164
|
};
|
|
17735
18165
|
this.wsManager.onDisconnect(onDisconnect);
|
|
18166
|
+
const abortHandler = () => {
|
|
18167
|
+
settleReject(new Error("Tool execution aborted"));
|
|
18168
|
+
};
|
|
17736
18169
|
if (abortSignal) {
|
|
17737
18170
|
if (abortSignal.aborted) {
|
|
17738
18171
|
settleReject(new Error("Tool execution aborted"));
|
|
17739
18172
|
return;
|
|
17740
18173
|
}
|
|
17741
|
-
abortSignal.addEventListener("abort",
|
|
17742
|
-
once: true
|
|
17743
|
-
});
|
|
18174
|
+
abortSignal.addEventListener("abort", abortHandler, { once: true });
|
|
17744
18175
|
}
|
|
17745
18176
|
this.wsManager.onRequest(requestId, (message) => {
|
|
17746
18177
|
if (message.action === "cli_tool_response") {
|
|
@@ -18163,26 +18594,14 @@ var SubagentOrchestrator = class {
|
|
|
18163
18594
|
const available = this.deps.agentStore.getAgentNames().join(", ");
|
|
18164
18595
|
throw new Error(`Unknown agent: "${agentName}". Available agents: ${available}`);
|
|
18165
18596
|
}
|
|
18166
|
-
|
|
18167
|
-
if (!model && !agentDef.modelResolved) {
|
|
18168
|
-
const config = await this.deps.configStore.get();
|
|
18169
|
-
if (config?.defaultModel) {
|
|
18170
|
-
this.deps.logger.debug(
|
|
18171
|
-
`Agent "${agentName}" model unresolved, inheriting main session model: ${config.defaultModel}`
|
|
18172
|
-
);
|
|
18173
|
-
effectiveModel = config.defaultModel;
|
|
18174
|
-
}
|
|
18175
|
-
}
|
|
18597
|
+
const effectiveModel = model || agentDef.model;
|
|
18176
18598
|
const effectiveThoroughness = thoroughness || agentDef.defaultThoroughness;
|
|
18177
18599
|
const maxIterations = agentDef.maxIterations[effectiveThoroughness];
|
|
18178
18600
|
const effectiveVariables = {
|
|
18179
18601
|
...agentDef.defaultVariables,
|
|
18180
18602
|
...variables
|
|
18181
18603
|
};
|
|
18182
|
-
let systemPrompt = this.substituteVariables(agentDef.systemPrompt, task, effectiveVariables
|
|
18183
|
-
maxIterations,
|
|
18184
|
-
thoroughness: effectiveThoroughness
|
|
18185
|
-
});
|
|
18604
|
+
let systemPrompt = this.substituteVariables(agentDef.systemPrompt, task, effectiveVariables);
|
|
18186
18605
|
const effectiveAllowedTools = allowedTools || agentDef.allowedTools;
|
|
18187
18606
|
const toolFilter = {
|
|
18188
18607
|
allowedTools: effectiveAllowedTools,
|
|
@@ -18200,7 +18619,10 @@ var SubagentOrchestrator = class {
|
|
|
18200
18619
|
this.deps.showPermissionPrompt,
|
|
18201
18620
|
agentContext,
|
|
18202
18621
|
this.deps.configStore,
|
|
18203
|
-
this.deps.apiClient
|
|
18622
|
+
this.deps.apiClient,
|
|
18623
|
+
void 0,
|
|
18624
|
+
// toolFilter (applied separately below)
|
|
18625
|
+
this.deps.checkpointStore
|
|
18204
18626
|
);
|
|
18205
18627
|
const filteredTools = filterToolsByPatterns2(allTools, toolFilter.allowedTools, toolFilter.deniedTools);
|
|
18206
18628
|
if (this.deps.customCommandStore) {
|
|
@@ -18299,15 +18721,11 @@ var SubagentOrchestrator = class {
|
|
|
18299
18721
|
}
|
|
18300
18722
|
/**
|
|
18301
18723
|
* Substitute variables in system prompt
|
|
18302
|
-
* Reserved: $TASK, $
|
|
18724
|
+
* Reserved: $TASK, $ARGUMENTS, $1, $2, etc.
|
|
18303
18725
|
*/
|
|
18304
|
-
substituteVariables(systemPrompt, task, variables
|
|
18726
|
+
substituteVariables(systemPrompt, task, variables) {
|
|
18305
18727
|
let result = systemPrompt;
|
|
18306
18728
|
result = result.split("$TASK").join(task);
|
|
18307
|
-
if (reserved) {
|
|
18308
|
-
result = result.split("$MAX_ITERATIONS").join(String(reserved.maxIterations));
|
|
18309
|
-
result = result.split("$THOROUGHNESS").join(reserved.thoroughness);
|
|
18310
|
-
}
|
|
18311
18729
|
if (variables) {
|
|
18312
18730
|
for (const [key, value] of Object.entries(variables)) {
|
|
18313
18731
|
result = result.split(`$${key}`).join(value);
|
|
@@ -18357,8 +18775,8 @@ var SubagentOrchestrator = class {
|
|
|
18357
18775
|
};
|
|
18358
18776
|
|
|
18359
18777
|
// src/agents/AgentStore.ts
|
|
18360
|
-
import
|
|
18361
|
-
import
|
|
18778
|
+
import fs13 from "fs/promises";
|
|
18779
|
+
import path18 from "path";
|
|
18362
18780
|
import os2 from "os";
|
|
18363
18781
|
import matter2 from "gray-matter";
|
|
18364
18782
|
var FULL_MODEL_ID_PREFIXES = [
|
|
@@ -18487,7 +18905,7 @@ function resolveModelAlias(modelInput, agentName, filePath) {
|
|
|
18487
18905
|
}
|
|
18488
18906
|
const availableAliases = getAvailableModelAliases();
|
|
18489
18907
|
const suggestions = availableAliases.filter((alias) => alias.includes(normalizedInput) || normalizedInput.includes(alias)).slice(0, 5);
|
|
18490
|
-
let warning = `Unknown model "${modelInput}" in agent "${agentName}" (${filePath}).
|
|
18908
|
+
let warning = `Unknown model "${modelInput}" in agent "${agentName}" (${filePath}). Using inherited model instead.
|
|
18491
18909
|
`;
|
|
18492
18910
|
if (suggestions.length > 0) {
|
|
18493
18911
|
warning += `Did you mean: ${suggestions.join(", ")}?
|
|
@@ -18508,10 +18926,10 @@ var AgentStore = class {
|
|
|
18508
18926
|
const root = projectRoot || process.cwd();
|
|
18509
18927
|
const home = os2.homedir();
|
|
18510
18928
|
this.builtinAgentsDir = builtinDir;
|
|
18511
|
-
this.globalB4MAgentsDir =
|
|
18512
|
-
this.globalClaudeAgentsDir =
|
|
18513
|
-
this.projectB4MAgentsDir =
|
|
18514
|
-
this.projectClaudeAgentsDir =
|
|
18929
|
+
this.globalB4MAgentsDir = path18.join(home, ".bike4mind", "agents");
|
|
18930
|
+
this.globalClaudeAgentsDir = path18.join(home, ".claude", "agents");
|
|
18931
|
+
this.projectB4MAgentsDir = path18.join(root, ".bike4mind", "agents");
|
|
18932
|
+
this.projectClaudeAgentsDir = path18.join(root, ".claude", "agents");
|
|
18515
18933
|
}
|
|
18516
18934
|
/**
|
|
18517
18935
|
* Load all agents from all directories
|
|
@@ -18535,7 +18953,7 @@ var AgentStore = class {
|
|
|
18535
18953
|
*/
|
|
18536
18954
|
async loadAgentsFromDirectory(directory, source) {
|
|
18537
18955
|
try {
|
|
18538
|
-
const stats = await
|
|
18956
|
+
const stats = await fs13.stat(directory);
|
|
18539
18957
|
if (!stats.isDirectory()) {
|
|
18540
18958
|
return;
|
|
18541
18959
|
}
|
|
@@ -18563,9 +18981,9 @@ var AgentStore = class {
|
|
|
18563
18981
|
async findAgentFiles(directory) {
|
|
18564
18982
|
const files = [];
|
|
18565
18983
|
try {
|
|
18566
|
-
const entries = await
|
|
18984
|
+
const entries = await fs13.readdir(directory, { withFileTypes: true });
|
|
18567
18985
|
for (const entry of entries) {
|
|
18568
|
-
const fullPath =
|
|
18986
|
+
const fullPath = path18.join(directory, entry.name);
|
|
18569
18987
|
if (entry.isDirectory()) {
|
|
18570
18988
|
const subFiles = await this.findAgentFiles(fullPath);
|
|
18571
18989
|
files.push(...subFiles);
|
|
@@ -18582,10 +19000,10 @@ var AgentStore = class {
|
|
|
18582
19000
|
* Parse a single agent markdown file
|
|
18583
19001
|
*/
|
|
18584
19002
|
async parseAgentFile(filePath, source) {
|
|
18585
|
-
const content = await
|
|
19003
|
+
const content = await fs13.readFile(filePath, "utf-8");
|
|
18586
19004
|
const { data: frontmatter, content: body } = matter2(content);
|
|
18587
19005
|
const parsed = AgentFrontmatterSchema.parse(frontmatter);
|
|
18588
|
-
const name =
|
|
19006
|
+
const name = path18.basename(filePath, ".md");
|
|
18589
19007
|
const modelInput = parsed.model || DEFAULT_AGENT_MODEL;
|
|
18590
19008
|
const resolution = resolveModelAlias(modelInput, name, filePath);
|
|
18591
19009
|
if (!resolution.resolved && resolution.warning) {
|
|
@@ -18610,8 +19028,7 @@ var AgentStore = class {
|
|
|
18610
19028
|
defaultVariables: parsed.variables,
|
|
18611
19029
|
hooks: parsed.hooks,
|
|
18612
19030
|
source,
|
|
18613
|
-
filePath
|
|
18614
|
-
modelResolved: resolution.resolved
|
|
19031
|
+
filePath
|
|
18615
19032
|
};
|
|
18616
19033
|
}
|
|
18617
19034
|
/**
|
|
@@ -18666,16 +19083,16 @@ var AgentStore = class {
|
|
|
18666
19083
|
*/
|
|
18667
19084
|
async createAgentFile(name, isGlobal = false, useClaude = true) {
|
|
18668
19085
|
const targetDir = isGlobal ? useClaude ? this.globalClaudeAgentsDir : this.globalB4MAgentsDir : useClaude ? this.projectClaudeAgentsDir : this.projectB4MAgentsDir;
|
|
18669
|
-
const filePath =
|
|
19086
|
+
const filePath = path18.join(targetDir, `${name}.md`);
|
|
18670
19087
|
try {
|
|
18671
|
-
await
|
|
19088
|
+
await fs13.access(filePath);
|
|
18672
19089
|
throw new Error(`Agent file already exists: ${filePath}`);
|
|
18673
19090
|
} catch (error) {
|
|
18674
19091
|
if (error.code !== "ENOENT") {
|
|
18675
19092
|
throw error;
|
|
18676
19093
|
}
|
|
18677
19094
|
}
|
|
18678
|
-
await
|
|
19095
|
+
await fs13.mkdir(targetDir, { recursive: true });
|
|
18679
19096
|
const template = `---
|
|
18680
19097
|
description: ${name} agent description
|
|
18681
19098
|
model: claude-3-5-haiku-20241022
|
|
@@ -18710,7 +19127,7 @@ You are a ${name} specialist. Your job is to [describe primary task].
|
|
|
18710
19127
|
## Output Format
|
|
18711
19128
|
Describe the expected output format here.
|
|
18712
19129
|
`;
|
|
18713
|
-
await
|
|
19130
|
+
await fs13.writeFile(filePath, template, "utf-8");
|
|
18714
19131
|
return filePath;
|
|
18715
19132
|
}
|
|
18716
19133
|
/**
|
|
@@ -19363,7 +19780,7 @@ function createTodoStore(onUpdate) {
|
|
|
19363
19780
|
|
|
19364
19781
|
// src/tools/findDefinitionTool.ts
|
|
19365
19782
|
import { stat as stat3 } from "fs/promises";
|
|
19366
|
-
import
|
|
19783
|
+
import path19 from "path";
|
|
19367
19784
|
import { execFile as execFile2 } from "child_process";
|
|
19368
19785
|
import { promisify as promisify2 } from "util";
|
|
19369
19786
|
import { createRequire as createRequire2 } from "module";
|
|
@@ -19384,8 +19801,8 @@ var ALL_KEYWORDS = Object.values(KIND_KEYWORDS).flat();
|
|
|
19384
19801
|
function getRipgrepPath2() {
|
|
19385
19802
|
try {
|
|
19386
19803
|
const ripgrepPath = require3.resolve("@vscode/ripgrep");
|
|
19387
|
-
const ripgrepDir =
|
|
19388
|
-
return
|
|
19804
|
+
const ripgrepDir = path19.dirname(ripgrepPath);
|
|
19805
|
+
return path19.join(ripgrepDir, "..", "bin", "rg" + (process.platform === "win32" ? ".exe" : ""));
|
|
19389
19806
|
} catch {
|
|
19390
19807
|
throw new Error(
|
|
19391
19808
|
"ripgrep is not available. Please install @vscode/ripgrep: pnpm add @vscode/ripgrep --filter @bike4mind/services"
|
|
@@ -19393,10 +19810,10 @@ function getRipgrepPath2() {
|
|
|
19393
19810
|
}
|
|
19394
19811
|
}
|
|
19395
19812
|
function isPathWithinWorkspace2(targetPath, baseCwd) {
|
|
19396
|
-
const resolvedTarget =
|
|
19397
|
-
const resolvedBase =
|
|
19398
|
-
const relativePath =
|
|
19399
|
-
return !relativePath.startsWith("..") && !
|
|
19813
|
+
const resolvedTarget = path19.resolve(targetPath);
|
|
19814
|
+
const resolvedBase = path19.resolve(baseCwd);
|
|
19815
|
+
const relativePath = path19.relative(resolvedBase, resolvedTarget);
|
|
19816
|
+
return !relativePath.startsWith("..") && !path19.isAbsolute(relativePath);
|
|
19400
19817
|
}
|
|
19401
19818
|
function escapeRegex(str) {
|
|
19402
19819
|
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
@@ -19429,7 +19846,7 @@ async function findDefinitions(params) {
|
|
|
19429
19846
|
throw new Error("symbol_name is required");
|
|
19430
19847
|
}
|
|
19431
19848
|
const baseCwd = process.cwd();
|
|
19432
|
-
const targetDir = search_path ?
|
|
19849
|
+
const targetDir = search_path ? path19.resolve(baseCwd, search_path) : baseCwd;
|
|
19433
19850
|
if (!isPathWithinWorkspace2(targetDir, baseCwd)) {
|
|
19434
19851
|
throw new Error(`Path validation failed: "${search_path}" resolves outside the allowed workspace directory`);
|
|
19435
19852
|
}
|
|
@@ -19484,7 +19901,7 @@ async function findDefinitions(params) {
|
|
|
19484
19901
|
const lineText = match.data.lines.text.trimEnd();
|
|
19485
19902
|
if (isLikelyDefinition(lineText)) {
|
|
19486
19903
|
allMatches.push({
|
|
19487
|
-
filePath:
|
|
19904
|
+
filePath: path19.relative(targetDir, match.data.path.text) || path19.basename(match.data.path.text),
|
|
19488
19905
|
lineNumber: match.data.line_number,
|
|
19489
19906
|
line: lineText
|
|
19490
19907
|
});
|
|
@@ -19562,8 +19979,8 @@ function createFindDefinitionTool() {
|
|
|
19562
19979
|
}
|
|
19563
19980
|
|
|
19564
19981
|
// src/tools/getFileStructure/index.ts
|
|
19565
|
-
import { existsSync as
|
|
19566
|
-
import
|
|
19982
|
+
import { existsSync as existsSync12, promises as fs14, statSync as statSync6 } from "fs";
|
|
19983
|
+
import path20 from "path";
|
|
19567
19984
|
|
|
19568
19985
|
// src/tools/getFileStructure/formatter.ts
|
|
19569
19986
|
var SECTIONS = [
|
|
@@ -19600,35 +20017,35 @@ function formatSection(lines, title, items, format) {
|
|
|
19600
20017
|
}
|
|
19601
20018
|
|
|
19602
20019
|
// src/tools/getFileStructure/index.ts
|
|
19603
|
-
var
|
|
20020
|
+
var MAX_FILE_SIZE4 = 10 * 1024 * 1024;
|
|
19604
20021
|
function createGetFileStructureTool() {
|
|
19605
20022
|
return {
|
|
19606
20023
|
toolFn: async (value) => {
|
|
19607
20024
|
const params = value;
|
|
19608
20025
|
try {
|
|
19609
20026
|
const cwd = process.cwd();
|
|
19610
|
-
const resolvedPath =
|
|
20027
|
+
const resolvedPath = path20.resolve(cwd, params.path);
|
|
19611
20028
|
if (!resolvedPath.startsWith(cwd)) {
|
|
19612
20029
|
return "Error: Access denied - cannot read files outside of current working directory";
|
|
19613
20030
|
}
|
|
19614
|
-
if (!
|
|
20031
|
+
if (!existsSync12(resolvedPath)) {
|
|
19615
20032
|
return `Error: File not found: ${params.path}`;
|
|
19616
20033
|
}
|
|
19617
20034
|
const stats = statSync6(resolvedPath);
|
|
19618
20035
|
if (stats.isDirectory()) {
|
|
19619
20036
|
return `Error: Path is a directory, not a file: ${params.path}`;
|
|
19620
20037
|
}
|
|
19621
|
-
if (stats.size >
|
|
19622
|
-
return `Error: File too large (${(stats.size / 1024 / 1024).toFixed(2)}MB). Max: ${
|
|
20038
|
+
if (stats.size > MAX_FILE_SIZE4) {
|
|
20039
|
+
return `Error: File too large (${(stats.size / 1024 / 1024).toFixed(2)}MB). Max: ${MAX_FILE_SIZE4 / 1024 / 1024}MB`;
|
|
19623
20040
|
}
|
|
19624
|
-
const ext =
|
|
20041
|
+
const ext = path20.extname(resolvedPath).toLowerCase();
|
|
19625
20042
|
const { getLanguageForExtension, parseFileStructure, getSupportedLanguages } = await import("./treeSitterEngine-4SGFQDY3.js");
|
|
19626
20043
|
const languageId = getLanguageForExtension(ext);
|
|
19627
20044
|
if (!languageId) {
|
|
19628
20045
|
const supported = getSupportedLanguages();
|
|
19629
20046
|
return `Error: Unsupported file type "${ext}". Supported languages: ${supported.join(", ")}`;
|
|
19630
20047
|
}
|
|
19631
|
-
const sourceCode = await
|
|
20048
|
+
const sourceCode = await fs14.readFile(resolvedPath, "utf-8");
|
|
19632
20049
|
const lineCount = sourceCode.split("\n").length;
|
|
19633
20050
|
const items = await parseFileStructure(sourceCode, languageId);
|
|
19634
20051
|
return formatStructureOutput(params.path, items, stats.size, lineCount);
|
|
@@ -19690,7 +20107,8 @@ function CliApp() {
|
|
|
19690
20107
|
abortController: null,
|
|
19691
20108
|
contextContent: "",
|
|
19692
20109
|
backgroundManager: null,
|
|
19693
|
-
wsManager: null
|
|
20110
|
+
wsManager: null,
|
|
20111
|
+
checkpointStore: null
|
|
19694
20112
|
});
|
|
19695
20113
|
const [isInitialized, setIsInitialized] = useState10(false);
|
|
19696
20114
|
const [initError, setInitError] = useState10(null);
|
|
@@ -19944,6 +20362,12 @@ function CliApp() {
|
|
|
19944
20362
|
enqueuePermissionPrompt(prompt);
|
|
19945
20363
|
});
|
|
19946
20364
|
};
|
|
20365
|
+
const checkpointProjectDir = state.configStore.getProjectConfigDir() || process.cwd();
|
|
20366
|
+
const checkpointStore = new CheckpointStore(checkpointProjectDir);
|
|
20367
|
+
try {
|
|
20368
|
+
await checkpointStore.init(newSession.id);
|
|
20369
|
+
} catch {
|
|
20370
|
+
}
|
|
19947
20371
|
const agentContext = {
|
|
19948
20372
|
currentAgent: null,
|
|
19949
20373
|
observationQueue: []
|
|
@@ -19956,7 +20380,10 @@ function CliApp() {
|
|
|
19956
20380
|
promptFn,
|
|
19957
20381
|
agentContext,
|
|
19958
20382
|
state.configStore,
|
|
19959
|
-
apiClient
|
|
20383
|
+
apiClient,
|
|
20384
|
+
void 0,
|
|
20385
|
+
// toolFilter
|
|
20386
|
+
checkpointStore
|
|
19960
20387
|
);
|
|
19961
20388
|
startupLog.push(`\u{1F6E0}\uFE0F Loaded ${b4mTools2.length} B4M tool(s)`);
|
|
19962
20389
|
const mcpManager = new McpManager(config);
|
|
@@ -19987,7 +20414,8 @@ function CliApp() {
|
|
|
19987
20414
|
apiClient,
|
|
19988
20415
|
agentStore,
|
|
19989
20416
|
customCommandStore: state.customCommandStore,
|
|
19990
|
-
enableParallelToolExecution: config.preferences.enableParallelToolExecution === true
|
|
20417
|
+
enableParallelToolExecution: config.preferences.enableParallelToolExecution === true,
|
|
20418
|
+
checkpointStore
|
|
19991
20419
|
});
|
|
19992
20420
|
const backgroundManager = new BackgroundAgentManager(orchestrator);
|
|
19993
20421
|
backgroundManager.setOnStatusChange((job) => {
|
|
@@ -20110,8 +20538,10 @@ function CliApp() {
|
|
|
20110
20538
|
// Store raw context for compact instructions
|
|
20111
20539
|
backgroundManager,
|
|
20112
20540
|
// Store for grouped notification turn tracking
|
|
20113
|
-
wsManager
|
|
20541
|
+
wsManager,
|
|
20114
20542
|
// WebSocket connection manager (null if using SSE fallback)
|
|
20543
|
+
checkpointStore
|
|
20544
|
+
// File change checkpointing for undo/restore
|
|
20115
20545
|
}));
|
|
20116
20546
|
setStoreSession(newSession);
|
|
20117
20547
|
const bannerLines = [
|
|
@@ -20804,6 +21234,10 @@ Available commands:
|
|
|
20804
21234
|
/exit - Exit the CLI
|
|
20805
21235
|
/clear - Start a new session
|
|
20806
21236
|
/rewind - Rewind conversation to a previous point
|
|
21237
|
+
/undo - Undo the last file change
|
|
21238
|
+
/checkpoints - List available file restore points
|
|
21239
|
+
/restore <n> - Restore files to a specific checkpoint
|
|
21240
|
+
/diff [n] - Show diff between current state and a checkpoint
|
|
20807
21241
|
/login - Authenticate with your B4M account
|
|
20808
21242
|
/logout - Clear authentication and sign out
|
|
20809
21243
|
/whoami - Show current authenticated user
|
|
@@ -20896,6 +21330,9 @@ Keyboard Shortcuts:
|
|
|
20896
21330
|
}
|
|
20897
21331
|
await logger.initialize(loadedSession.id);
|
|
20898
21332
|
logger.debug("=== Session Resumed ===");
|
|
21333
|
+
if (state.checkpointStore) {
|
|
21334
|
+
state.checkpointStore.setSessionId(loadedSession.id);
|
|
21335
|
+
}
|
|
20899
21336
|
setState((prev) => ({ ...prev, session: loadedSession }));
|
|
20900
21337
|
setStoreSession(loadedSession);
|
|
20901
21338
|
useCliStore.getState().clearPendingMessages();
|
|
@@ -21134,6 +21571,9 @@ Keyboard Shortcuts:
|
|
|
21134
21571
|
};
|
|
21135
21572
|
await logger.initialize(newSession.id);
|
|
21136
21573
|
logger.debug("=== New Session Started via /clear ===");
|
|
21574
|
+
if (state.checkpointStore) {
|
|
21575
|
+
state.checkpointStore.setSessionId(newSession.id);
|
|
21576
|
+
}
|
|
21137
21577
|
setState((prev) => ({ ...prev, session: newSession }));
|
|
21138
21578
|
setStoreSession(newSession);
|
|
21139
21579
|
useCliStore.getState().clearPendingMessages();
|
|
@@ -21144,8 +21584,7 @@ Keyboard Shortcuts:
|
|
|
21144
21584
|
`);
|
|
21145
21585
|
break;
|
|
21146
21586
|
}
|
|
21147
|
-
case "rewind":
|
|
21148
|
-
case "undo": {
|
|
21587
|
+
case "rewind": {
|
|
21149
21588
|
if (!state.session) {
|
|
21150
21589
|
console.log("No active session to rewind");
|
|
21151
21590
|
return;
|
|
@@ -21204,6 +21643,103 @@ Keyboard Shortcuts:
|
|
|
21204
21643
|
}));
|
|
21205
21644
|
break;
|
|
21206
21645
|
}
|
|
21646
|
+
case "undo": {
|
|
21647
|
+
if (!state.checkpointStore) {
|
|
21648
|
+
console.log("Checkpointing not available.");
|
|
21649
|
+
return;
|
|
21650
|
+
}
|
|
21651
|
+
const undoCheckpoints = state.checkpointStore.listCheckpoints();
|
|
21652
|
+
if (undoCheckpoints.length === 0) {
|
|
21653
|
+
console.log("No checkpoints available. No file changes have been made yet.");
|
|
21654
|
+
return;
|
|
21655
|
+
}
|
|
21656
|
+
try {
|
|
21657
|
+
const restored = await state.checkpointStore.undoLast();
|
|
21658
|
+
console.log(`
|
|
21659
|
+
\u2705 Restored ${restored.filePaths.length} file(s) to state before: ${restored.name}`);
|
|
21660
|
+
for (const f of restored.filePaths) {
|
|
21661
|
+
console.log(` - ${f}`);
|
|
21662
|
+
}
|
|
21663
|
+
console.log("");
|
|
21664
|
+
} catch (err) {
|
|
21665
|
+
console.log(`Failed to undo: ${err instanceof Error ? err.message : String(err)}`);
|
|
21666
|
+
}
|
|
21667
|
+
break;
|
|
21668
|
+
}
|
|
21669
|
+
case "checkpoints": {
|
|
21670
|
+
if (!state.checkpointStore) {
|
|
21671
|
+
console.log("Checkpointing not available.");
|
|
21672
|
+
return;
|
|
21673
|
+
}
|
|
21674
|
+
const cpList = state.checkpointStore.listCheckpoints();
|
|
21675
|
+
if (cpList.length === 0) {
|
|
21676
|
+
console.log("No checkpoints yet. Checkpoints are created automatically before file changes.");
|
|
21677
|
+
return;
|
|
21678
|
+
}
|
|
21679
|
+
console.log("\nCheckpoints (most recent first):\n");
|
|
21680
|
+
cpList.forEach((cp, idx) => {
|
|
21681
|
+
const ageMs = Date.now() - new Date(cp.timestamp).getTime();
|
|
21682
|
+
const ageSec = Math.floor(ageMs / 1e3);
|
|
21683
|
+
let age;
|
|
21684
|
+
if (ageSec < 60) age = `${ageSec}s ago`;
|
|
21685
|
+
else if (ageSec < 3600) age = `${Math.floor(ageSec / 60)}m ago`;
|
|
21686
|
+
else age = `${Math.floor(ageSec / 3600)}h ago`;
|
|
21687
|
+
console.log(` ${idx + 1}. ${cp.name} (${age}) - ${cp.filePaths.length} file(s)`);
|
|
21688
|
+
});
|
|
21689
|
+
console.log("\nUse /restore <number> to restore, /diff <number> to see changes.\n");
|
|
21690
|
+
break;
|
|
21691
|
+
}
|
|
21692
|
+
case "restore": {
|
|
21693
|
+
if (!state.checkpointStore) {
|
|
21694
|
+
console.log("Checkpointing not available.");
|
|
21695
|
+
return;
|
|
21696
|
+
}
|
|
21697
|
+
const restoreTarget = args[0];
|
|
21698
|
+
if (!restoreTarget) {
|
|
21699
|
+
console.log("Usage: /restore <checkpoint-number>");
|
|
21700
|
+
console.log("Use /checkpoints to list available restore points.");
|
|
21701
|
+
return;
|
|
21702
|
+
}
|
|
21703
|
+
const restoreNum = parseInt(restoreTarget, 10);
|
|
21704
|
+
if (isNaN(restoreNum) || restoreNum < 1) {
|
|
21705
|
+
console.log("Please provide a valid checkpoint number. Use /checkpoints to list.");
|
|
21706
|
+
return;
|
|
21707
|
+
}
|
|
21708
|
+
try {
|
|
21709
|
+
const restoredCp = await state.checkpointStore.restoreCheckpoint(restoreNum);
|
|
21710
|
+
console.log(`
|
|
21711
|
+
\u2705 Restored to checkpoint: ${restoredCp.name}`);
|
|
21712
|
+
for (const f of restoredCp.filePaths) {
|
|
21713
|
+
console.log(` - ${f}`);
|
|
21714
|
+
}
|
|
21715
|
+
console.log("");
|
|
21716
|
+
} catch (err) {
|
|
21717
|
+
console.log(`Failed to restore: ${err instanceof Error ? err.message : String(err)}`);
|
|
21718
|
+
}
|
|
21719
|
+
break;
|
|
21720
|
+
}
|
|
21721
|
+
case "diff": {
|
|
21722
|
+
if (!state.checkpointStore) {
|
|
21723
|
+
console.log("Checkpointing not available.");
|
|
21724
|
+
return;
|
|
21725
|
+
}
|
|
21726
|
+
const diffTarget = args[0] ? parseInt(args[0], 10) : 1;
|
|
21727
|
+
if (isNaN(diffTarget) || diffTarget < 1) {
|
|
21728
|
+
console.log("Usage: /diff [checkpoint-number] (defaults to 1, most recent)");
|
|
21729
|
+
return;
|
|
21730
|
+
}
|
|
21731
|
+
try {
|
|
21732
|
+
const diffOutput = state.checkpointStore.getCheckpointDiff(diffTarget);
|
|
21733
|
+
if (!diffOutput.trim()) {
|
|
21734
|
+
console.log("No differences found between checkpoint and current state.");
|
|
21735
|
+
} else {
|
|
21736
|
+
console.log(diffOutput);
|
|
21737
|
+
}
|
|
21738
|
+
} catch (err) {
|
|
21739
|
+
console.log(`Failed to generate diff: ${err instanceof Error ? err.message : String(err)}`);
|
|
21740
|
+
}
|
|
21741
|
+
break;
|
|
21742
|
+
}
|
|
21207
21743
|
case "usage": {
|
|
21208
21744
|
const usageAuthTokens = await state.configStore.getAuthTokens();
|
|
21209
21745
|
if (!usageAuthTokens || new Date(usageAuthTokens.expiresAt) <= /* @__PURE__ */ new Date()) {
|