@bike4mind/cli 0.2.31-feat-dynamic-agent-creation.19590 → 0.2.31-feat-python-playground.19625

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,22 +1,22 @@
1
1
  #!/usr/bin/env node
2
2
  import "./chunk-GQGOWACU.js";
3
- import "./chunk-4P27WV34.js";
4
- import "./chunk-S6AUIWWB.js";
3
+ import "./chunk-ZHXVJFCI.js";
4
+ import "./chunk-RTD3GUAP.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-DQHCE3TN.js";
11
+ } from "./chunk-LAZAO77H.js";
12
12
  import {
13
13
  ConfigStore,
14
14
  logger
15
- } from "./chunk-ZS65NK25.js";
15
+ } from "./chunk-32PKF3N7.js";
16
16
  import {
17
17
  checkForUpdate,
18
18
  package_default
19
- } from "./chunk-HNT6CPNA.js";
19
+ } from "./chunk-S3S2FV2N.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-PV6PZFPC.js";
35
+ } from "./chunk-2PXPXXDN.js";
36
36
  import {
37
37
  AiEvents,
38
38
  ApiKeyEvents,
@@ -90,7 +90,7 @@ import {
90
90
  getMcpProviderMetadata,
91
91
  getViewById,
92
92
  resolveNavigationIntents
93
- } from "./chunk-PLA2VBQW.js";
93
+ } from "./chunk-EEGKRFVP.js";
94
94
  import {
95
95
  Logger
96
96
  } from "./chunk-PFBYGCOW.js";
@@ -733,8 +733,25 @@ var COMMANDS = [
733
733
  },
734
734
  {
735
735
  name: "rewind",
736
- description: "Rewind conversation to a previous point",
737
- aliases: ["undo"]
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",
@@ -1647,19 +1664,6 @@ function buildConfigItems(availableModels) {
1647
1664
  exportFormat: value
1648
1665
  }
1649
1666
  })
1650
- },
1651
- {
1652
- key: "enableDynamicAgentCreation",
1653
- label: "Dynamic Agents",
1654
- type: "boolean",
1655
- getValue: (config) => config.preferences.enableDynamicAgentCreation ?? false,
1656
- setValue: (config, value) => ({
1657
- ...config,
1658
- preferences: {
1659
- ...config.preferences,
1660
- enableDynamicAgentCreation: value
1661
- }
1662
- })
1663
1667
  }
1664
1668
  );
1665
1669
  return items;
@@ -2851,9 +2855,346 @@ var CommandHistoryStore = class {
2851
2855
  }
2852
2856
  };
2853
2857
 
2854
- // src/storage/CustomCommandStore.ts
2855
- import fs5 from "fs/promises";
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";
2856
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";
2857
3198
  import os from "os";
2858
3199
 
2859
3200
  // src/utils/commandParser.ts
@@ -3017,14 +3358,14 @@ var CustomCommandStore = class {
3017
3358
  const home = os.homedir();
3018
3359
  const root = projectRoot || process.cwd();
3019
3360
  this.globalCommandsDirs = [
3020
- path6.join(home, ".bike4mind", "commands"),
3021
- path6.join(home, ".claude", "commands"),
3022
- path6.join(home, ".claude", "skills")
3361
+ path7.join(home, ".bike4mind", "commands"),
3362
+ path7.join(home, ".claude", "commands"),
3363
+ path7.join(home, ".claude", "skills")
3023
3364
  ];
3024
3365
  this.projectCommandsDirs = [
3025
- path6.join(root, ".bike4mind", "commands"),
3026
- path6.join(root, ".claude", "commands"),
3027
- path6.join(root, ".claude", "skills")
3366
+ path7.join(root, ".bike4mind", "commands"),
3367
+ path7.join(root, ".claude", "commands"),
3368
+ path7.join(root, ".claude", "skills")
3028
3369
  ];
3029
3370
  }
3030
3371
  /**
@@ -3049,7 +3390,7 @@ var CustomCommandStore = class {
3049
3390
  */
3050
3391
  async loadCommandsFromDirectory(directory, source) {
3051
3392
  try {
3052
- const stats = await fs5.stat(directory);
3393
+ const stats = await fs6.stat(directory);
3053
3394
  if (!stats.isDirectory()) {
3054
3395
  return;
3055
3396
  }
@@ -3082,9 +3423,9 @@ var CustomCommandStore = class {
3082
3423
  async findCommandFiles(directory) {
3083
3424
  const files = [];
3084
3425
  try {
3085
- const entries = await fs5.readdir(directory, { withFileTypes: true });
3426
+ const entries = await fs6.readdir(directory, { withFileTypes: true });
3086
3427
  for (const entry of entries) {
3087
- const fullPath = path6.join(directory, entry.name);
3428
+ const fullPath = path7.join(directory, entry.name);
3088
3429
  if (entry.isDirectory()) {
3089
3430
  const subFiles = await this.findCommandFiles(fullPath);
3090
3431
  files.push(...subFiles);
@@ -3104,14 +3445,14 @@ var CustomCommandStore = class {
3104
3445
  * @param source - Source identifier ('global' or 'project')
3105
3446
  */
3106
3447
  async loadCommandFile(filePath, source) {
3107
- const filename = path6.basename(filePath);
3448
+ const filename = path7.basename(filePath);
3108
3449
  const isSkillFile = filename.toLowerCase() === "skill.md";
3109
3450
  const commandName = isSkillFile ? this.extractSkillName(filePath) : extractCommandName(filename);
3110
3451
  if (!commandName) {
3111
3452
  console.warn(`Invalid command filename: ${filename} (must end with .md and have valid name)`);
3112
3453
  return;
3113
3454
  }
3114
- const fileContent = await fs5.readFile(filePath, "utf-8");
3455
+ const fileContent = await fs6.readFile(filePath, "utf-8");
3115
3456
  const command = parseCommandFile(fileContent, filePath, commandName, source);
3116
3457
  this.commands.set(commandName, command);
3117
3458
  }
@@ -3123,7 +3464,7 @@ var CustomCommandStore = class {
3123
3464
  * @returns Skill name or null if invalid
3124
3465
  */
3125
3466
  extractSkillName(filePath) {
3126
- const parentDir = path6.basename(path6.dirname(filePath));
3467
+ const parentDir = path7.basename(path7.dirname(filePath));
3127
3468
  return parentDir && parentDir !== "skills" ? parentDir : null;
3128
3469
  }
3129
3470
  /**
@@ -3185,15 +3526,15 @@ var CustomCommandStore = class {
3185
3526
  */
3186
3527
  async createCommandFile(name, isGlobal = false) {
3187
3528
  const targetDir = isGlobal ? this.globalCommandsDirs[0] : this.projectCommandsDirs[0];
3188
- const filePath = path6.join(targetDir, `${name}.md`);
3189
- const fileExists = await fs5.access(filePath).then(
3529
+ const filePath = path7.join(targetDir, `${name}.md`);
3530
+ const fileExists = await fs6.access(filePath).then(
3190
3531
  () => true,
3191
3532
  () => false
3192
3533
  );
3193
3534
  if (fileExists) {
3194
3535
  throw new Error(`Command file already exists: ${filePath}`);
3195
3536
  }
3196
- await fs5.mkdir(targetDir, { recursive: true });
3537
+ await fs6.mkdir(targetDir, { recursive: true });
3197
3538
  const template = `---
3198
3539
  description: ${name} command
3199
3540
  argument-hint: [args]
@@ -3208,7 +3549,7 @@ You can use:
3208
3549
  - $1, $2, etc. for positional arguments
3209
3550
  - @filename for file references
3210
3551
  `;
3211
- await fs5.writeFile(filePath, template, "utf-8");
3552
+ await fs6.writeFile(filePath, template, "utf-8");
3212
3553
  return filePath;
3213
3554
  }
3214
3555
  };
@@ -3868,13 +4209,11 @@ var TOOL_CREATE_FILE = "create_file";
3868
4209
  var TOOL_BASH_EXECUTE = "bash_execute";
3869
4210
  var TOOL_SUBAGENT_DELEGATE = "subagent_delegate";
3870
4211
  var TOOL_WRITE_TODOS = "write_todos";
3871
- var TOOL_CREATE_DYNAMIC_AGENT = "create_dynamic_agent";
3872
4212
  var EXPLORE_SUBAGENT_TYPE = "explore";
3873
4213
  function buildCoreSystemPrompt(contextSection, config) {
3874
4214
  let projectContextSection = "";
3875
4215
  let agentDirectoryContext = "";
3876
4216
  let skillsSection = "";
3877
- let dynamicAgentSection = "";
3878
4217
  if (typeof contextSection === "string") {
3879
4218
  projectContextSection = contextSection;
3880
4219
  if (config) {
@@ -3884,9 +4223,6 @@ function buildCoreSystemPrompt(contextSection, config) {
3884
4223
  if (config.agentStore) {
3885
4224
  agentDirectoryContext = config.agentStore.getDirectoryContext();
3886
4225
  }
3887
- if (config.enableDynamicAgentCreation) {
3888
- dynamicAgentSection = buildDynamicAgentPromptSection();
3889
- }
3890
4226
  }
3891
4227
  } else if (contextSection && typeof contextSection === "object") {
3892
4228
  config = contextSection;
@@ -3905,9 +4241,6 @@ ${config.contextContent}`;
3905
4241
  if (config.agentStore) {
3906
4242
  agentDirectoryContext = config.agentStore.getDirectoryContext();
3907
4243
  }
3908
- if (config.enableDynamicAgentCreation) {
3909
- dynamicAgentSection = buildDynamicAgentPromptSection();
3910
- }
3911
4244
  }
3912
4245
  return `You are an autonomous AI assistant with access to tools. Your job is to help users by taking action and solving problems proactively.
3913
4246
 
@@ -3940,7 +4273,6 @@ SUBAGENT DELEGATION:
3940
4273
  - You have access to specialized subagents via the ${TOOL_SUBAGENT_DELEGATE} tool
3941
4274
  - ${agentDirectoryContext ? `${agentDirectoryContext}` : ""}
3942
4275
  - Subagents keep the main conversation clean and run faster with optimized models
3943
- ${dynamicAgentSection}
3944
4276
 
3945
4277
  CODE SEARCH BEST PRACTICES:
3946
4278
  When searching code, follow this hierarchy for speed and efficiency:
@@ -3999,52 +4331,6 @@ EXAMPLES:
3999
4331
 
4000
4332
  Remember: Use context from previous messages to understand follow-up questions.${projectContextSection}${skillsSection}`;
4001
4333
  }
4002
- function buildDynamicAgentPromptSection() {
4003
- return `
4004
- DYNAMIC AGENT CREATION:
4005
- You have access to the '${TOOL_CREATE_DYNAMIC_AGENT}' tool, which lets you create and spawn one-off agents at runtime with custom system prompts.
4006
-
4007
- **When to use '${TOOL_CREATE_DYNAMIC_AGENT}' instead of '${TOOL_SUBAGENT_DELEGATE}':**
4008
- - When no pre-defined agent fits the task \u2014 you need custom instructions or a specialized role
4009
- - When you want to give a sub-task a tailored system prompt (e.g., "You are a security auditor...")
4010
- - When you need fine-grained control over which tools the spawned agent can access
4011
-
4012
- **How to use it:**
4013
- 1. Provide a descriptive 'name' (e.g., "security-auditor", "migration-checker")
4014
- 2. Write a focused 'systemPrompt' that defines the agent's role and constraints
4015
- 3. Specify the 'task' \u2014 what the agent should accomplish
4016
- 4. Optionally restrict tools with 'allowedTools' or 'deniedTools'
4017
- 5. Use 'run_in_background: true' for long-running or parallel work
4018
-
4019
- **Constraints:** Dynamic agents cannot spawn other agents (no recursion). They are ephemeral and not saved.
4020
-
4021
- **Examples (simple \u2192 complex):**
4022
-
4023
- 1. **Security Auditor** \u2014 Review a file for vulnerabilities:
4024
- name: "security-auditor"
4025
- systemPrompt: "You are a security auditor. Review code for common vulnerabilities: injection attacks, hardcoded secrets, unsafe user input handling, and OWASP Top 10 issues. Report findings with severity (critical/high/medium/low), the affected line or pattern, and a recommended fix."
4026
- task: "Audit src/auth/login.ts for security vulnerabilities"
4027
- allowedTools: ["file_read", "grep_search"]
4028
-
4029
- 2. **Test Gap Analyzer** \u2014 Find untested modules:
4030
- name: "test-gap-analyzer"
4031
- systemPrompt: "You are a test coverage analyst. Compare source files against test files to identify modules that lack tests. For each gap, note the source file, its key exports/functions, and why it should be tested. Prioritize files with complex logic or external integrations."
4032
- task: "Analyze apps/cli/src/agents/ and identify source files without corresponding test coverage"
4033
- thoroughness: "very_thorough"
4034
-
4035
- 3. **Refactoring Planner** \u2014 Analyze and propose refactoring:
4036
- name: "refactor-planner"
4037
- systemPrompt: "You are a senior software architect specializing in code quality. Analyze the target module for code smells: long functions (>50 lines), deep nesting (>3 levels), god objects, duplicated logic, unclear naming, and tight coupling. Propose a concrete refactoring strategy with specific steps, expected benefits, and risk assessment for each change."
4038
- task: "Analyze src/core/prompts.ts and propose a refactoring plan"
4039
- thoroughness: "very_thorough"
4040
-
4041
- 4. **PR Description Writer** \u2014 Generate PR descriptions from diffs:
4042
- name: "pr-writer"
4043
- systemPrompt: "You are a technical writer who creates clear, structured pull request descriptions. Given a git diff or list of changes, produce: a concise summary (1-3 sentences), a bulleted list of specific changes grouped by category, and a testing checklist. Use imperative mood. Focus on the 'why' not just the 'what'."
4044
- task: "Generate a PR description for the current branch changes"
4045
- allowedTools: ["bash_execute", "file_read"]
4046
- thoroughness: "quick"`;
4047
- }
4048
4334
 
4049
4335
  // ../../b4m-core/packages/services/dist/src/referService/generateCodes.js
4050
4336
  import { randomBytes } from "crypto";
@@ -4495,7 +4781,7 @@ import { GoogleGenerativeAI } from "@google/generative-ai";
4495
4781
  import OpenAI2 from "openai";
4496
4782
 
4497
4783
  // ../../b4m-core/packages/services/dist/src/importHistoryService/index.js
4498
- import fs6, { unlinkSync } from "fs";
4784
+ import fs7, { unlinkSync as unlinkSync2 } from "fs";
4499
4785
  import yauzl from "yauzl";
4500
4786
  import axios3 from "axios";
4501
4787
 
@@ -6186,8 +6472,8 @@ async function processAndStoreImages(images, context) {
6186
6472
  const buffer = await downloadImage(image);
6187
6473
  const fileType = await fileTypeFromBuffer2(buffer);
6188
6474
  const filename = `${uuidv45()}.${fileType?.ext}`;
6189
- const path19 = await context.imageGenerateStorage.upload(buffer, filename, {});
6190
- return path19;
6475
+ const path21 = await context.imageGenerateStorage.upload(buffer, filename, {});
6476
+ return path21;
6191
6477
  }));
6192
6478
  }
6193
6479
  async function updateQuestAndReturnMarkdown(storedImageUrls, context) {
@@ -7513,8 +7799,8 @@ async function processAndStoreImage(imageUrl, context) {
7513
7799
  const buffer = await downloadImage2(imageUrl);
7514
7800
  const fileType = await fileTypeFromBuffer3(buffer);
7515
7801
  const filename = `${uuidv46()}.${fileType?.ext}`;
7516
- const path19 = await context.imageGenerateStorage.upload(buffer, filename, {});
7517
- return path19;
7802
+ const path21 = await context.imageGenerateStorage.upload(buffer, filename, {});
7803
+ return path21;
7518
7804
  }
7519
7805
  async function generateFullMask(imageBuffer) {
7520
7806
  const sharp = (await import("sharp")).default;
@@ -9455,33 +9741,33 @@ var planetVisibilityTool = {
9455
9741
  };
9456
9742
 
9457
9743
  // ../../b4m-core/packages/services/dist/src/llm/tools/implementation/fileRead/index.js
9458
- import { promises as fs7 } from "fs";
9459
- import { existsSync as existsSync4, statSync as statSync4 } from "fs";
9460
- import path7 from "path";
9461
- var MAX_FILE_SIZE2 = 10 * 1024 * 1024;
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;
9462
9748
  async function readFileContent(params) {
9463
9749
  const { path: filePath, encoding = "utf-8", offset = 0, limit } = params;
9464
- const normalizedPath = path7.normalize(filePath);
9465
- const resolvedPath = path7.resolve(process.cwd(), normalizedPath);
9466
- const cwd = path7.resolve(process.cwd());
9750
+ const normalizedPath = path8.normalize(filePath);
9751
+ const resolvedPath = path8.resolve(process.cwd(), normalizedPath);
9752
+ const cwd = path8.resolve(process.cwd());
9467
9753
  if (!resolvedPath.startsWith(cwd)) {
9468
9754
  throw new Error(`Access denied: Cannot read files outside of current working directory`);
9469
9755
  }
9470
- if (!existsSync4(resolvedPath)) {
9756
+ if (!existsSync5(resolvedPath)) {
9471
9757
  throw new Error(`File not found: ${filePath}`);
9472
9758
  }
9473
9759
  const stats = statSync4(resolvedPath);
9474
9760
  if (stats.isDirectory()) {
9475
9761
  throw new Error(`Path is a directory, not a file: ${filePath}`);
9476
9762
  }
9477
- if (stats.size > MAX_FILE_SIZE2) {
9478
- throw new Error(`File too large: ${(stats.size / 1024 / 1024).toFixed(2)}MB (max ${MAX_FILE_SIZE2 / 1024 / 1024}MB)`);
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)`);
9479
9765
  }
9480
9766
  const isBinary = await checkIfBinary(resolvedPath);
9481
9767
  if (isBinary && encoding === "utf-8") {
9482
9768
  throw new Error(`File appears to be binary. Use encoding 'base64' to read binary files, or specify a different encoding.`);
9483
9769
  }
9484
- const content = await fs7.readFile(resolvedPath, encoding);
9770
+ const content = await fs8.readFile(resolvedPath, encoding);
9485
9771
  if (typeof content === "string") {
9486
9772
  const lines = content.split("\n");
9487
9773
  const totalLines = lines.length;
@@ -9519,7 +9805,7 @@ ${content}`;
9519
9805
  }
9520
9806
  async function checkIfBinary(filePath) {
9521
9807
  const buffer = Buffer.alloc(8192);
9522
- const fd = await fs7.open(filePath, "r");
9808
+ const fd = await fs8.open(filePath, "r");
9523
9809
  try {
9524
9810
  const { bytesRead } = await fd.read(buffer, 0, 8192, 0);
9525
9811
  const chunk = buffer.slice(0, bytesRead);
@@ -9536,7 +9822,7 @@ var fileReadTool = {
9536
9822
  context.logger.info("\u{1F4C4} FileRead: Reading file", { path: params.path });
9537
9823
  try {
9538
9824
  const content = await readFileContent(params);
9539
- const stats = statSync4(path7.resolve(process.cwd(), path7.normalize(params.path)));
9825
+ const stats = statSync4(path8.resolve(process.cwd(), path8.normalize(params.path)));
9540
9826
  context.logger.info("\u2705 FileRead: Success", {
9541
9827
  path: params.path,
9542
9828
  size: stats.size,
@@ -9580,25 +9866,25 @@ var fileReadTool = {
9580
9866
  };
9581
9867
 
9582
9868
  // ../../b4m-core/packages/services/dist/src/llm/tools/implementation/createFile/index.js
9583
- import { promises as fs8 } from "fs";
9584
- import { existsSync as existsSync5 } from "fs";
9585
- import path8 from "path";
9869
+ import { promises as fs9 } from "fs";
9870
+ import { existsSync as existsSync6 } from "fs";
9871
+ import path9 from "path";
9586
9872
  async function createFile(params) {
9587
9873
  const { path: filePath, content, createDirectories = true } = params;
9588
- const normalizedPath = path8.normalize(filePath);
9589
- const resolvedPath = path8.resolve(process.cwd(), normalizedPath);
9590
- const cwd = path8.resolve(process.cwd());
9874
+ const normalizedPath = path9.normalize(filePath);
9875
+ const resolvedPath = path9.resolve(process.cwd(), normalizedPath);
9876
+ const cwd = path9.resolve(process.cwd());
9591
9877
  if (!resolvedPath.startsWith(cwd)) {
9592
9878
  throw new Error(`Access denied: Cannot create files outside of current working directory`);
9593
9879
  }
9594
- const fileExists = existsSync5(resolvedPath);
9880
+ const fileExists = existsSync6(resolvedPath);
9595
9881
  const action = fileExists ? "overwritten" : "created";
9596
9882
  if (createDirectories) {
9597
- const dir = path8.dirname(resolvedPath);
9598
- await fs8.mkdir(dir, { recursive: true });
9883
+ const dir = path9.dirname(resolvedPath);
9884
+ await fs9.mkdir(dir, { recursive: true });
9599
9885
  }
9600
- await fs8.writeFile(resolvedPath, content, "utf-8");
9601
- const stats = await fs8.stat(resolvedPath);
9886
+ await fs9.writeFile(resolvedPath, content, "utf-8");
9887
+ const stats = await fs9.stat(resolvedPath);
9602
9888
  const lines = content.split("\n").length;
9603
9889
  return `File ${action} successfully: ${filePath}
9604
9890
  Size: ${stats.size} bytes
@@ -9609,7 +9895,7 @@ var createFileTool = {
9609
9895
  implementation: (context) => ({
9610
9896
  toolFn: async (value) => {
9611
9897
  const params = value;
9612
- const fileExists = existsSync5(path8.resolve(process.cwd(), path8.normalize(params.path)));
9898
+ const fileExists = existsSync6(path9.resolve(process.cwd(), path9.normalize(params.path)));
9613
9899
  context.logger.info(`\u{1F4DD} CreateFile: ${fileExists ? "Overwriting" : "Creating"} file`, {
9614
9900
  path: params.path,
9615
9901
  size: params.content.length
@@ -9651,7 +9937,7 @@ var createFileTool = {
9651
9937
  // ../../b4m-core/packages/services/dist/src/llm/tools/implementation/globFiles/index.js
9652
9938
  import { glob } from "glob";
9653
9939
  import { stat } from "fs/promises";
9654
- import path9 from "path";
9940
+ import path10 from "path";
9655
9941
  var DEFAULT_IGNORE_PATTERNS = [
9656
9942
  "**/node_modules/**",
9657
9943
  "**/.git/**",
@@ -9667,7 +9953,7 @@ var DEFAULT_IGNORE_PATTERNS = [
9667
9953
  async function findFiles(params) {
9668
9954
  const { pattern, dir_path, case_sensitive = true, respect_git_ignore = true } = params;
9669
9955
  const baseCwd = process.cwd();
9670
- const targetDir = dir_path ? path9.resolve(baseCwd, path9.normalize(dir_path)) : baseCwd;
9956
+ const targetDir = dir_path ? path10.resolve(baseCwd, path10.normalize(dir_path)) : baseCwd;
9671
9957
  if (!targetDir.startsWith(baseCwd)) {
9672
9958
  throw new Error(`Access denied: Cannot search outside of current working directory`);
9673
9959
  }
@@ -9707,7 +9993,7 @@ async function findFiles(params) {
9707
9993
  const summary = `Found ${filesWithStats.length} file(s)${truncated ? ` (showing first ${MAX_RESULTS})` : ""} matching: ${pattern}`;
9708
9994
  const dirInfo = dir_path ? `
9709
9995
  Directory: ${dir_path}` : "";
9710
- const filesList = results.map((file) => path9.relative(baseCwd, file.path)).join("\n");
9996
+ const filesList = results.map((file) => path10.relative(baseCwd, file.path)).join("\n");
9711
9997
  return `${summary}${dirInfo}
9712
9998
 
9713
9999
  ${filesList}`;
@@ -9761,27 +10047,48 @@ var globFilesTool = {
9761
10047
 
9762
10048
  // ../../b4m-core/packages/services/dist/src/llm/tools/implementation/grepSearch/index.js
9763
10049
  import { stat as stat2 } from "fs/promises";
9764
- import path10 from "path";
9765
- import { execFile } from "child_process";
10050
+ import { existsSync as existsSync7 } from "fs";
10051
+ import path11 from "path";
10052
+ import { execFile, execFileSync as execFileSync2 } from "child_process";
9766
10053
  import { promisify } from "util";
9767
10054
  import { createRequire } from "module";
9768
10055
  var execFileAsync = promisify(execFile);
9769
10056
  var require2 = createRequire(import.meta.url);
10057
+ var cachedRgPath = null;
9770
10058
  function getRipgrepPath() {
10059
+ if (cachedRgPath)
10060
+ return cachedRgPath;
9771
10061
  try {
9772
10062
  const ripgrepPath = require2.resolve("@vscode/ripgrep");
9773
- const ripgrepDir = path10.dirname(ripgrepPath);
9774
- const rgBinary = path10.join(ripgrepDir, "..", "bin", "rg" + (process.platform === "win32" ? ".exe" : ""));
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;
9775
10079
  return rgBinary;
9776
10080
  } catch (error) {
10081
+ if (error instanceof Error && error.message.includes("ripgrep binary not found")) {
10082
+ throw error;
10083
+ }
9777
10084
  throw new Error("ripgrep is not available. Please install @vscode/ripgrep: pnpm add @vscode/ripgrep --filter @bike4mind/services");
9778
10085
  }
9779
10086
  }
9780
10087
  function isPathWithinWorkspace(targetPath, baseCwd) {
9781
- const resolvedTarget = path10.resolve(targetPath);
9782
- const resolvedBase = path10.resolve(baseCwd);
9783
- const relativePath = path10.relative(resolvedBase, resolvedTarget);
9784
- return !relativePath.startsWith("..") && !path10.isAbsolute(relativePath);
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);
9785
10092
  }
9786
10093
  function convertGlobToRipgrepGlobs(globPattern) {
9787
10094
  if (!globPattern || globPattern === "**/*") {
@@ -9797,7 +10104,7 @@ function convertGlobToRipgrepGlobs(globPattern) {
9797
10104
  async function searchFiles2(params) {
9798
10105
  const { pattern, dir_path, include } = params;
9799
10106
  const baseCwd = process.cwd();
9800
- const targetDir = dir_path ? path10.resolve(baseCwd, dir_path) : baseCwd;
10107
+ const targetDir = dir_path ? path11.resolve(baseCwd, dir_path) : baseCwd;
9801
10108
  if (!isPathWithinWorkspace(targetDir, baseCwd)) {
9802
10109
  throw new Error(`Path validation failed: "${dir_path}" resolves outside the allowed workspace directory`);
9803
10110
  }
@@ -9859,7 +10166,7 @@ async function searchFiles2(params) {
9859
10166
  if (item.type === "match") {
9860
10167
  const match = item;
9861
10168
  allMatches.push({
9862
- filePath: path10.relative(targetDir, match.data.path.text) || path10.basename(match.data.path.text),
10169
+ filePath: path11.relative(targetDir, match.data.path.text) || path11.basename(match.data.path.text),
9863
10170
  lineNumber: match.data.line_number,
9864
10171
  line: match.data.lines.text.trimEnd()
9865
10172
  // Remove trailing newline
@@ -9952,18 +10259,18 @@ var grepSearchTool = {
9952
10259
  };
9953
10260
 
9954
10261
  // ../../b4m-core/packages/services/dist/src/llm/tools/implementation/deleteFile/index.js
9955
- import { promises as fs9 } from "fs";
9956
- import { existsSync as existsSync6, statSync as statSync5 } from "fs";
9957
- import path11 from "path";
10262
+ import { promises as fs10 } from "fs";
10263
+ import { existsSync as existsSync8, statSync as statSync5 } from "fs";
10264
+ import path12 from "path";
9958
10265
  async function deleteFile(params) {
9959
10266
  const { path: filePath, recursive = false } = params;
9960
- const normalizedPath = path11.normalize(filePath);
9961
- const resolvedPath = path11.resolve(process.cwd(), normalizedPath);
9962
- const cwd = path11.resolve(process.cwd());
10267
+ const normalizedPath = path12.normalize(filePath);
10268
+ const resolvedPath = path12.resolve(process.cwd(), normalizedPath);
10269
+ const cwd = path12.resolve(process.cwd());
9963
10270
  if (!resolvedPath.startsWith(cwd)) {
9964
10271
  throw new Error(`Access denied: Cannot delete files outside of current working directory`);
9965
10272
  }
9966
- if (!existsSync6(resolvedPath)) {
10273
+ if (!existsSync8(resolvedPath)) {
9967
10274
  throw new Error(`File or directory not found: ${filePath}`);
9968
10275
  }
9969
10276
  const stats = statSync5(resolvedPath);
@@ -9973,10 +10280,10 @@ async function deleteFile(params) {
9973
10280
  throw new Error(`Path is a directory: ${filePath}. Use recursive=true to delete directories and their contents.`);
9974
10281
  }
9975
10282
  if (isDirectory) {
9976
- await fs9.rm(resolvedPath, { recursive: true, force: true });
10283
+ await fs10.rm(resolvedPath, { recursive: true, force: true });
9977
10284
  return `Directory deleted successfully: ${filePath}`;
9978
10285
  } else {
9979
- await fs9.unlink(resolvedPath);
10286
+ await fs10.unlink(resolvedPath);
9980
10287
  return `File deleted successfully: ${filePath}
9981
10288
  Size: ${size} bytes`;
9982
10289
  }
@@ -9986,8 +10293,8 @@ var deleteFileTool = {
9986
10293
  implementation: (context) => ({
9987
10294
  toolFn: async (value) => {
9988
10295
  const params = value;
9989
- const resolvedPath = path11.resolve(process.cwd(), path11.normalize(params.path));
9990
- const isDirectory = existsSync6(resolvedPath) && statSync5(resolvedPath).isDirectory();
10296
+ const resolvedPath = path12.resolve(process.cwd(), path12.normalize(params.path));
10297
+ const isDirectory = existsSync8(resolvedPath) && statSync5(resolvedPath).isDirectory();
9991
10298
  context.logger.info(`\u{1F5D1}\uFE0F DeleteFile: Deleting ${isDirectory ? "directory" : "file"}`, {
9992
10299
  path: params.path,
9993
10300
  recursive: params.recursive
@@ -11564,6 +11871,7 @@ var solverMetadata = {
11564
11871
  requires: "Backend compute",
11565
11872
  available: false,
11566
11873
  tagline: "Quantum-inspired classical simulation",
11874
+ externalUrl: "https://q.bike4mind.com/jobs/e499f34a-d40e-4cfb-98e5-47e0a2091f3d",
11567
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.",
11568
11876
  howItWorks: [
11569
11877
  "Encode scheduling problem as QUBO matrix",
@@ -11609,6 +11917,7 @@ var solverMetadata = {
11609
11917
  requires: "IonQ API key + credits",
11610
11918
  available: false,
11611
11919
  tagline: "Real quantum hardware optimization",
11920
+ externalUrl: "https://cloud.ionq.com/jobs/019c07ff-205a-76d0-9f83-24098ea09589",
11612
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.",
11613
11922
  howItWorks: [
11614
11923
  "Encode scheduling problem as QUBO matrix",
@@ -12666,7 +12975,7 @@ var navigateViewTool = {
12666
12975
 
12667
12976
  // ../../b4m-core/packages/services/dist/src/llm/tools/implementation/bashExecute/index.js
12668
12977
  import { spawn } from "child_process";
12669
- import path12 from "path";
12978
+ import path13 from "path";
12670
12979
  var DEFAULT_TIMEOUT_MS = 6e4;
12671
12980
  var MAX_OUTPUT_SIZE = 100 * 1024;
12672
12981
  var DANGEROUS_PATTERNS = [
@@ -12850,7 +13159,7 @@ async function executeBashCommand(params) {
12850
13159
  };
12851
13160
  }
12852
13161
  const baseCwd = process.cwd();
12853
- const targetCwd = relativeCwd ? path12.resolve(baseCwd, relativeCwd) : baseCwd;
13162
+ const targetCwd = relativeCwd ? path13.resolve(baseCwd, relativeCwd) : baseCwd;
12854
13163
  const effectiveTimeout = Math.min(timeout, 5 * 60 * 1e3);
12855
13164
  return new Promise((resolve3) => {
12856
13165
  let stdout = "";
@@ -13853,9 +14162,9 @@ function parseQuery(query) {
13853
14162
  }
13854
14163
 
13855
14164
  // ../../b4m-core/packages/services/dist/src/llm/tools/implementation/editLocalFile/index.js
13856
- import { promises as fs10 } from "fs";
13857
- import { existsSync as existsSync7 } from "fs";
13858
- import path13 from "path";
14165
+ import { promises as fs11 } from "fs";
14166
+ import { existsSync as existsSync9 } from "fs";
14167
+ import path14 from "path";
13859
14168
  import { diffLines as diffLines3 } from "diff";
13860
14169
  function generateDiff(original, modified) {
13861
14170
  const differences = diffLines3(original, modified);
@@ -13879,16 +14188,16 @@ function generateDiff(original, modified) {
13879
14188
  }
13880
14189
  async function editLocalFile(params) {
13881
14190
  const { path: filePath, old_string, new_string } = params;
13882
- const normalizedPath = path13.normalize(filePath);
13883
- const resolvedPath = path13.resolve(process.cwd(), normalizedPath);
13884
- const cwd = path13.resolve(process.cwd());
14191
+ const normalizedPath = path14.normalize(filePath);
14192
+ const resolvedPath = path14.resolve(process.cwd(), normalizedPath);
14193
+ const cwd = path14.resolve(process.cwd());
13885
14194
  if (!resolvedPath.startsWith(cwd)) {
13886
14195
  throw new Error(`Access denied: Cannot edit files outside of current working directory`);
13887
14196
  }
13888
- if (!existsSync7(resolvedPath)) {
14197
+ if (!existsSync9(resolvedPath)) {
13889
14198
  throw new Error(`File not found: ${filePath}`);
13890
14199
  }
13891
- const currentContent = await fs10.readFile(resolvedPath, "utf-8");
14200
+ const currentContent = await fs11.readFile(resolvedPath, "utf-8");
13892
14201
  if (!currentContent.includes(old_string)) {
13893
14202
  const preview = old_string.length > 100 ? old_string.substring(0, 100) + "..." : old_string;
13894
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}"`);
@@ -13898,7 +14207,7 @@ async function editLocalFile(params) {
13898
14207
  throw new Error(`Found ${occurrences} occurrences of the string to replace. Please provide a more specific old_string that matches exactly one location.`);
13899
14208
  }
13900
14209
  const newContent = currentContent.replace(old_string, new_string);
13901
- await fs10.writeFile(resolvedPath, newContent, "utf-8");
14210
+ await fs11.writeFile(resolvedPath, newContent, "utf-8");
13902
14211
  const diffResult = generateDiff(old_string, new_string);
13903
14212
  return `File edited successfully: ${filePath}
13904
14213
  Changes: +${diffResult.additions} lines, -${diffResult.deletions} lines
@@ -14547,12 +14856,27 @@ var GithubManagerAgent = (config) => ({
14547
14856
  exclusiveMcpServers: ["github"],
14548
14857
  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.
14549
14858
 
14550
- ## First Step: Resolve Repository Context
14551
- ALWAYS start by calling the \`github__current_user\` tool. The response includes:
14859
+ ## First Step: ALWAYS Resolve Repository Context Automatically
14860
+ Before doing ANYTHING else, call the \`github__current_user\` tool. The response includes:
14552
14861
  - **selected_repositories**: The list of repositories the user has enabled for AI access (in \`owner/repo\` format)
14553
14862
  - **user**: The authenticated user's profile (login, name, etc.)
14554
14863
 
14555
- Use this to resolve the owner and repo name when the user does not specify them. If only one repository is selected, default to that repository. If multiple are selected and the user's request is ambiguous, mention the available repositories and ask for clarification via your response.
14864
+ ### Repository Resolution Rules (CRITICAL)
14865
+ 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:
14866
+
14867
+ 1. **Exact match**: If the user says a repo name that exactly matches a repo name in selected_repositories, use it immediately.
14868
+ 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).
14869
+ 3. **Single repo shortcut**: If only one repository is selected, ALWAYS default to it \u2014 no questions asked.
14870
+ 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.
14871
+ 5. **No match**: If nothing in selected_repositories matches, tell the user which repos are available and ask them to clarify.
14872
+
14873
+ **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.
14874
+
14875
+ ### Defaults for Ambiguous Requests
14876
+ - **Timezone**: Default to UTC unless the user specifies otherwise.
14877
+ - **PR/Issue state**: Default to \`open\` unless the user specifies otherwise.
14878
+ - **Scope**: All matching repos unless the user narrows it down.
14879
+ - **When in doubt, act**: Prefer making reasonable assumptions and proceeding over asking clarifying questions. You can always note your assumptions in the response.
14556
14880
 
14557
14881
  ## Capabilities
14558
14882
 
@@ -14593,7 +14917,7 @@ Use this to resolve the owner and repo name when the user does not specify them.
14593
14917
  ## Output Format
14594
14918
  Provide a clear summary of actions taken:
14595
14919
  1. What was done (created, searched, merged, etc.)
14596
- 2. Links to relevant items (e.g., owner/repo#123, PR URLs)
14920
+ 2. Always provide links to relevant items (e.g., owner/repo#123, PR URLs)
14597
14921
  3. Any issues or warnings encountered
14598
14922
 
14599
14923
  Be precise with repository names, issue numbers, and PR numbers. Your results will be used by the main agent.`
@@ -15288,10 +15612,10 @@ var ToolErrorType;
15288
15612
  // src/utils/diffPreview.ts
15289
15613
  import * as Diff from "diff";
15290
15614
  import { readFile } from "fs/promises";
15291
- import { existsSync as existsSync8 } from "fs";
15615
+ import { existsSync as existsSync10 } from "fs";
15292
15616
  async function generateFileDiffPreview(args) {
15293
15617
  try {
15294
- if (!existsSync8(args.path)) {
15618
+ if (!existsSync10(args.path)) {
15295
15619
  const lines2 = args.content.split("\n");
15296
15620
  const preview = lines2.slice(0, 20).join("\n");
15297
15621
  const hasMore = lines2.length > 20;
@@ -15329,10 +15653,10 @@ ${diffLines4.join("\n")}`;
15329
15653
  }
15330
15654
  async function generateFileDeletePreview(args) {
15331
15655
  try {
15332
- if (!existsSync8(args.path)) {
15656
+ if (!existsSync10(args.path)) {
15333
15657
  return `[File does not exist: ${args.path}]`;
15334
15658
  }
15335
- const stats = await import("fs/promises").then((fs14) => fs14.stat(args.path));
15659
+ const stats = await import("fs/promises").then((fs15) => fs15.stat(args.path));
15336
15660
  return `[File will be deleted]
15337
15661
 
15338
15662
  Path: ${args.path}
@@ -15631,25 +15955,26 @@ var DEFAULT_AGENT_MODEL = "claude-3-5-haiku-20241022";
15631
15955
  var DEFAULT_THOROUGHNESS = "medium";
15632
15956
 
15633
15957
  // src/utils/toolsAdapter.ts
15958
+ import path15 from "path";
15634
15959
  var NoOpStorage = class extends BaseStorage {
15635
15960
  async upload(input, destination, options) {
15636
15961
  return `/tmp/${destination}`;
15637
15962
  }
15638
- async download(path19) {
15963
+ async download(path21) {
15639
15964
  throw new Error("Download not supported in CLI");
15640
15965
  }
15641
- async delete(path19) {
15966
+ async delete(path21) {
15642
15967
  }
15643
- async getSignedUrl(path19) {
15644
- return `/tmp/${path19}`;
15968
+ async getSignedUrl(path21) {
15969
+ return `/tmp/${path21}`;
15645
15970
  }
15646
- getPublicUrl(path19) {
15647
- return `/tmp/${path19}`;
15971
+ getPublicUrl(path21) {
15972
+ return `/tmp/${path21}`;
15648
15973
  }
15649
- async getPreview(path19) {
15650
- return `/tmp/${path19}`;
15974
+ async getPreview(path21) {
15975
+ return `/tmp/${path21}`;
15651
15976
  }
15652
- async getMetadata(path19) {
15977
+ async getMetadata(path21) {
15653
15978
  return { size: 0, contentType: "application/octet-stream" };
15654
15979
  }
15655
15980
  };
@@ -15822,6 +16147,27 @@ function wrapToolWithHooks(tool, hooks, hookContext) {
15822
16147
  }
15823
16148
  };
15824
16149
  }
16150
+ var CHECKPOINT_TOOLS = /* @__PURE__ */ new Set(["create_file", "edit_local_file", "delete_file"]);
16151
+ function wrapToolWithCheckpointing(tool, checkpointStore) {
16152
+ if (!checkpointStore || !CHECKPOINT_TOOLS.has(tool.toolSchema.name)) {
16153
+ return tool;
16154
+ }
16155
+ const originalFn = tool.toolFn;
16156
+ const toolName = tool.toolSchema.name;
16157
+ return {
16158
+ ...tool,
16159
+ toolFn: async (args) => {
16160
+ const filePath = args?.path;
16161
+ if (filePath) {
16162
+ try {
16163
+ await checkpointStore.createCheckpoint(toolName, [filePath], `before-${toolName}-${path15.basename(filePath)}`);
16164
+ } catch {
16165
+ }
16166
+ }
16167
+ return originalFn(args);
16168
+ }
16169
+ };
16170
+ }
15825
16171
  var TOOL_NAME_MAPPING = {
15826
16172
  // Claude Code -> B4M
15827
16173
  read: "file_read",
@@ -15846,7 +16192,7 @@ function normalizeToolName(toolName) {
15846
16192
  }
15847
16193
  return TOOL_NAME_MAPPING[toolName] || toolName;
15848
16194
  }
15849
- function generateCliTools(userId, llm, model, permissionManager, showPermissionPrompt, agentContext, configStore, apiClient, toolFilter) {
16195
+ function generateCliTools(userId, llm, model, permissionManager, showPermissionPrompt, agentContext, configStore, apiClient, toolFilter, checkpointStore) {
15850
16196
  const logger2 = new CliLogger();
15851
16197
  const storage = new NoOpStorage();
15852
16198
  const user = {
@@ -15905,9 +16251,17 @@ function generateCliTools(userId, llm, model, permissionManager, showPermissionP
15905
16251
  // imageProcessorLambdaName (not needed for CLI)
15906
16252
  tools_to_generate
15907
16253
  );
15908
- let tools = Object.entries(toolsMap).map(
15909
- ([_, tool]) => wrapToolWithPermission(tool, permissionManager, showPermissionPrompt, agentContext, configStore, apiClient)
15910
- );
16254
+ let tools = Object.entries(toolsMap).map(([_, tool]) => {
16255
+ const permissionWrapped = wrapToolWithPermission(
16256
+ tool,
16257
+ permissionManager,
16258
+ showPermissionPrompt,
16259
+ agentContext,
16260
+ configStore,
16261
+ apiClient
16262
+ );
16263
+ return wrapToolWithCheckpointing(permissionWrapped, checkpointStore ?? null);
16264
+ });
15911
16265
  if (toolFilter) {
15912
16266
  const { allowedTools, deniedTools } = toolFilter;
15913
16267
  const normalizedAllowed = allowedTools?.map(normalizeToolName);
@@ -16085,8 +16439,8 @@ function getEnvironmentName(configApiConfig) {
16085
16439
  }
16086
16440
 
16087
16441
  // src/utils/contextLoader.ts
16088
- import * as fs11 from "fs";
16089
- import * as path14 from "path";
16442
+ import * as fs12 from "fs";
16443
+ import * as path16 from "path";
16090
16444
  import { homedir as homedir3 } from "os";
16091
16445
  var CONTEXT_FILE_SIZE_LIMIT = 100 * 1024;
16092
16446
  var PROJECT_CONTEXT_FILES = [
@@ -16107,9 +16461,9 @@ function formatFileSize2(bytes) {
16107
16461
  return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
16108
16462
  }
16109
16463
  function tryReadContextFile(dir, filename, source) {
16110
- const filePath = path14.join(dir, filename);
16464
+ const filePath = path16.join(dir, filename);
16111
16465
  try {
16112
- const stats = fs11.lstatSync(filePath);
16466
+ const stats = fs12.lstatSync(filePath);
16113
16467
  if (stats.isDirectory()) {
16114
16468
  return null;
16115
16469
  }
@@ -16123,7 +16477,7 @@ function tryReadContextFile(dir, filename, source) {
16123
16477
  error: `${source === "global" ? "Global" : "Project"} ${filename} exceeds 100KB limit (${formatFileSize2(stats.size)})`
16124
16478
  };
16125
16479
  }
16126
- const content = fs11.readFileSync(filePath, "utf-8");
16480
+ const content = fs12.readFileSync(filePath, "utf-8");
16127
16481
  return {
16128
16482
  filename,
16129
16483
  content,
@@ -16175,7 +16529,7 @@ ${project.content}`;
16175
16529
  }
16176
16530
  async function loadContextFiles(projectDir) {
16177
16531
  const errors = [];
16178
- const globalDir = path14.join(homedir3(), ".bike4mind");
16532
+ const globalDir = path16.join(homedir3(), ".bike4mind");
16179
16533
  const projectDirectory = projectDir || process.cwd();
16180
16534
  const [globalResult, projectResult] = await Promise.all([
16181
16535
  Promise.resolve(findContextFile(globalDir, GLOBAL_CONTEXT_FILES, "global")),
@@ -16446,8 +16800,8 @@ function substituteArguments(template, args) {
16446
16800
  // ../../b4m-core/packages/mcp/dist/src/client.js
16447
16801
  import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
16448
16802
  import { Client as Client2 } from "@modelcontextprotocol/sdk/client/index.js";
16449
- import path15 from "path";
16450
- import { existsSync as existsSync9, readdirSync as readdirSync3 } from "fs";
16803
+ import path17 from "path";
16804
+ import { existsSync as existsSync11, readdirSync as readdirSync3 } from "fs";
16451
16805
  var MCPClient = class {
16452
16806
  // Note: This class handles MCP server communication with repository filtering
16453
16807
  mcp;
@@ -16488,18 +16842,18 @@ var MCPClient = class {
16488
16842
  const root = process.env.INIT_CWD || process.cwd();
16489
16843
  const candidatePaths = [
16490
16844
  // When running from SST Lambda with node_modules structure (copyFiles)
16491
- path15.join(root, `node_modules/@bike4mind/mcp/dist/src/${this.serverName}/index.js`),
16845
+ path17.join(root, `node_modules/@bike4mind/mcp/dist/src/${this.serverName}/index.js`),
16492
16846
  // When running from SST Lambda deployed environment (/var/task)
16493
- path15.join(root, `b4m-core/packages/mcp/dist/src/${this.serverName}/index.js`),
16847
+ path17.join(root, `b4m-core/packages/mcp/dist/src/${this.serverName}/index.js`),
16494
16848
  // When running from SST Lambda (.sst/artifacts/mcpHandler-dev), navigate to monorepo root (3 levels up)
16495
- path15.join(root, `../../../b4m-core/packages/mcp/dist/src/${this.serverName}/index.js`),
16849
+ path17.join(root, `../../../b4m-core/packages/mcp/dist/src/${this.serverName}/index.js`),
16496
16850
  // When running from packages/client (Next.js app), navigate to monorepo root (2 levels up)
16497
- path15.join(root, `../../b4m-core/packages/mcp/dist/src/${this.serverName}/index.js`),
16851
+ path17.join(root, `../../b4m-core/packages/mcp/dist/src/${this.serverName}/index.js`),
16498
16852
  // Original paths (backward compatibility)
16499
- path15.join(root, `/b4m-core/packages/mcp/dist/src/${this.serverName}/index.js`),
16500
- path15.join(root, "core", "mcp", "servers", this.serverName, "dist", "index.js")
16853
+ path17.join(root, `/b4m-core/packages/mcp/dist/src/${this.serverName}/index.js`),
16854
+ path17.join(root, "core", "mcp", "servers", this.serverName, "dist", "index.js")
16501
16855
  ];
16502
- const serverScriptPath = candidatePaths.find((p) => existsSync9(p));
16856
+ const serverScriptPath = candidatePaths.find((p) => existsSync11(p));
16503
16857
  if (!serverScriptPath) {
16504
16858
  const getDirectories = (source) => readdirSync3(source, { withFileTypes: true }).filter((dirent) => dirent.isDirectory()).map((dirent) => dirent.name);
16505
16859
  console.error(`[MCP] Server script not found. Tried paths:`, candidatePaths);
@@ -17429,6 +17783,7 @@ var WebSocketLlmBackend = class {
17429
17783
  settled = true;
17430
17784
  this.wsManager.offRequest(requestId);
17431
17785
  this.wsManager.offDisconnect(onDisconnect);
17786
+ options.abortSignal?.removeEventListener("abort", abortHandler);
17432
17787
  action();
17433
17788
  };
17434
17789
  const settleResolve = () => settle(() => resolve3());
@@ -17438,19 +17793,16 @@ var WebSocketLlmBackend = class {
17438
17793
  settleReject(new Error("WebSocket connection lost during completion"));
17439
17794
  };
17440
17795
  this.wsManager.onDisconnect(onDisconnect);
17796
+ const abortHandler = () => {
17797
+ logger.debug("[WebSocketLlmBackend] Abort signal received");
17798
+ settleResolve();
17799
+ };
17441
17800
  if (options.abortSignal) {
17442
17801
  if (options.abortSignal.aborted) {
17443
17802
  settleResolve();
17444
17803
  return;
17445
17804
  }
17446
- options.abortSignal.addEventListener(
17447
- "abort",
17448
- () => {
17449
- logger.debug("[WebSocketLlmBackend] Abort signal received");
17450
- settleResolve();
17451
- },
17452
- { once: true }
17453
- );
17805
+ options.abortSignal.addEventListener("abort", abortHandler, { once: true });
17454
17806
  }
17455
17807
  const updateUsage = (usage) => {
17456
17808
  if (usage) {
@@ -17788,27 +18140,35 @@ var WebSocketToolExecutor = class {
17788
18140
  const requestId = uuidv412();
17789
18141
  return new Promise((resolve3, reject) => {
17790
18142
  let settled = false;
18143
+ let timeoutTimer;
17791
18144
  const settle = (action) => {
17792
18145
  if (settled) return;
17793
18146
  settled = true;
18147
+ clearTimeout(timeoutTimer);
17794
18148
  this.wsManager.offRequest(requestId);
17795
18149
  this.wsManager.offDisconnect(onDisconnect);
18150
+ abortSignal?.removeEventListener("abort", abortHandler);
17796
18151
  action();
17797
18152
  };
17798
18153
  const settleResolve = (result) => settle(() => resolve3(result));
17799
18154
  const settleReject = (err) => settle(() => reject(err));
18155
+ const TOOL_TIMEOUT_MS = 5 * 60 * 1e3;
18156
+ timeoutTimer = setTimeout(() => {
18157
+ settleReject(new Error(`Tool execution timed out after ${TOOL_TIMEOUT_MS / 1e3}s`));
18158
+ }, TOOL_TIMEOUT_MS);
17800
18159
  const onDisconnect = () => {
17801
18160
  settleReject(new Error("WebSocket connection lost during tool execution"));
17802
18161
  };
17803
18162
  this.wsManager.onDisconnect(onDisconnect);
18163
+ const abortHandler = () => {
18164
+ settleReject(new Error("Tool execution aborted"));
18165
+ };
17804
18166
  if (abortSignal) {
17805
18167
  if (abortSignal.aborted) {
17806
18168
  settleReject(new Error("Tool execution aborted"));
17807
18169
  return;
17808
18170
  }
17809
- abortSignal.addEventListener("abort", () => settleReject(new Error("Tool execution aborted")), {
17810
- once: true
17811
- });
18171
+ abortSignal.addEventListener("abort", abortHandler, { once: true });
17812
18172
  }
17813
18173
  this.wsManager.onRequest(requestId, (message) => {
17814
18174
  if (message.action === "cli_tool_response") {
@@ -18226,16 +18586,10 @@ var SubagentOrchestrator = class {
18226
18586
  */
18227
18587
  async delegateToAgent(options) {
18228
18588
  const { task, agentName, thoroughness, variables, parentSessionId, model, allowedTools } = options;
18229
- let agentDef;
18230
- if (options.agentDefinition) {
18231
- agentDef = { ...options.agentDefinition, name: agentName, source: "dynamic", filePath: "<dynamic>" };
18232
- } else {
18233
- const storedDef = this.deps.agentStore.getAgent(agentName);
18234
- if (!storedDef) {
18235
- const available = this.deps.agentStore.getAgentNames().join(", ");
18236
- throw new Error(`Unknown agent: "${agentName}". Available agents: ${available}`);
18237
- }
18238
- agentDef = storedDef;
18589
+ const agentDef = this.deps.agentStore.getAgent(agentName);
18590
+ if (!agentDef) {
18591
+ const available = this.deps.agentStore.getAgentNames().join(", ");
18592
+ throw new Error(`Unknown agent: "${agentName}". Available agents: ${available}`);
18239
18593
  }
18240
18594
  const effectiveModel = model || agentDef.model;
18241
18595
  const effectiveThoroughness = thoroughness || agentDef.defaultThoroughness;
@@ -18262,7 +18616,10 @@ var SubagentOrchestrator = class {
18262
18616
  this.deps.showPermissionPrompt,
18263
18617
  agentContext,
18264
18618
  this.deps.configStore,
18265
- this.deps.apiClient
18619
+ this.deps.apiClient,
18620
+ void 0,
18621
+ // toolFilter (applied separately below)
18622
+ this.deps.checkpointStore
18266
18623
  );
18267
18624
  const filteredTools = filterToolsByPatterns2(allTools, toolFilter.allowedTools, toolFilter.deniedTools);
18268
18625
  if (this.deps.customCommandStore) {
@@ -18415,8 +18772,8 @@ var SubagentOrchestrator = class {
18415
18772
  };
18416
18773
 
18417
18774
  // src/agents/AgentStore.ts
18418
- import fs12 from "fs/promises";
18419
- import path16 from "path";
18775
+ import fs13 from "fs/promises";
18776
+ import path18 from "path";
18420
18777
  import os2 from "os";
18421
18778
  import matter2 from "gray-matter";
18422
18779
  var FULL_MODEL_ID_PREFIXES = [
@@ -18566,10 +18923,10 @@ var AgentStore = class {
18566
18923
  const root = projectRoot || process.cwd();
18567
18924
  const home = os2.homedir();
18568
18925
  this.builtinAgentsDir = builtinDir;
18569
- this.globalB4MAgentsDir = path16.join(home, ".bike4mind", "agents");
18570
- this.globalClaudeAgentsDir = path16.join(home, ".claude", "agents");
18571
- this.projectB4MAgentsDir = path16.join(root, ".bike4mind", "agents");
18572
- this.projectClaudeAgentsDir = path16.join(root, ".claude", "agents");
18926
+ this.globalB4MAgentsDir = path18.join(home, ".bike4mind", "agents");
18927
+ this.globalClaudeAgentsDir = path18.join(home, ".claude", "agents");
18928
+ this.projectB4MAgentsDir = path18.join(root, ".bike4mind", "agents");
18929
+ this.projectClaudeAgentsDir = path18.join(root, ".claude", "agents");
18573
18930
  }
18574
18931
  /**
18575
18932
  * Load all agents from all directories
@@ -18593,7 +18950,7 @@ var AgentStore = class {
18593
18950
  */
18594
18951
  async loadAgentsFromDirectory(directory, source) {
18595
18952
  try {
18596
- const stats = await fs12.stat(directory);
18953
+ const stats = await fs13.stat(directory);
18597
18954
  if (!stats.isDirectory()) {
18598
18955
  return;
18599
18956
  }
@@ -18621,9 +18978,9 @@ var AgentStore = class {
18621
18978
  async findAgentFiles(directory) {
18622
18979
  const files = [];
18623
18980
  try {
18624
- const entries = await fs12.readdir(directory, { withFileTypes: true });
18981
+ const entries = await fs13.readdir(directory, { withFileTypes: true });
18625
18982
  for (const entry of entries) {
18626
- const fullPath = path16.join(directory, entry.name);
18983
+ const fullPath = path18.join(directory, entry.name);
18627
18984
  if (entry.isDirectory()) {
18628
18985
  const subFiles = await this.findAgentFiles(fullPath);
18629
18986
  files.push(...subFiles);
@@ -18640,10 +18997,10 @@ var AgentStore = class {
18640
18997
  * Parse a single agent markdown file
18641
18998
  */
18642
18999
  async parseAgentFile(filePath, source) {
18643
- const content = await fs12.readFile(filePath, "utf-8");
19000
+ const content = await fs13.readFile(filePath, "utf-8");
18644
19001
  const { data: frontmatter, content: body } = matter2(content);
18645
19002
  const parsed = AgentFrontmatterSchema.parse(frontmatter);
18646
- const name = path16.basename(filePath, ".md");
19003
+ const name = path18.basename(filePath, ".md");
18647
19004
  const modelInput = parsed.model || DEFAULT_AGENT_MODEL;
18648
19005
  const resolution = resolveModelAlias(modelInput, name, filePath);
18649
19006
  if (!resolution.resolved && resolution.warning) {
@@ -18723,16 +19080,16 @@ var AgentStore = class {
18723
19080
  */
18724
19081
  async createAgentFile(name, isGlobal = false, useClaude = true) {
18725
19082
  const targetDir = isGlobal ? useClaude ? this.globalClaudeAgentsDir : this.globalB4MAgentsDir : useClaude ? this.projectClaudeAgentsDir : this.projectB4MAgentsDir;
18726
- const filePath = path16.join(targetDir, `${name}.md`);
19083
+ const filePath = path18.join(targetDir, `${name}.md`);
18727
19084
  try {
18728
- await fs12.access(filePath);
19085
+ await fs13.access(filePath);
18729
19086
  throw new Error(`Agent file already exists: ${filePath}`);
18730
19087
  } catch (error) {
18731
19088
  if (error.code !== "ENOENT") {
18732
19089
  throw error;
18733
19090
  }
18734
19091
  }
18735
- await fs12.mkdir(targetDir, { recursive: true });
19092
+ await fs13.mkdir(targetDir, { recursive: true });
18736
19093
  const template = `---
18737
19094
  description: ${name} agent description
18738
19095
  model: claude-3-5-haiku-20241022
@@ -18767,7 +19124,7 @@ You are a ${name} specialist. Your job is to [describe primary task].
18767
19124
  ## Output Format
18768
19125
  Describe the expected output format here.
18769
19126
  `;
18770
- await fs12.writeFile(filePath, template, "utf-8");
19127
+ await fs13.writeFile(filePath, template, "utf-8");
18771
19128
  return filePath;
18772
19129
  }
18773
19130
  /**
@@ -18900,136 +19257,6 @@ ${agentDescriptions}
18900
19257
  };
18901
19258
  }
18902
19259
 
18903
- // src/agents/dynamicAgentTool.ts
18904
- function createDynamicAgentTool(orchestrator, parentSessionId, backgroundManager) {
18905
- return {
18906
- toolFn: async (args) => {
18907
- const params = args;
18908
- if (!params.task) {
18909
- throw new Error("create_dynamic_agent: task parameter is required");
18910
- }
18911
- if (!params.name) {
18912
- throw new Error("create_dynamic_agent: name parameter is required");
18913
- }
18914
- if (!/^[a-zA-Z0-9_-]+$/.test(params.name)) {
18915
- throw new Error(
18916
- "create_dynamic_agent: name must contain only alphanumeric characters, hyphens, and underscores"
18917
- );
18918
- }
18919
- if (!params.systemPrompt) {
18920
- throw new Error("create_dynamic_agent: systemPrompt parameter is required");
18921
- }
18922
- const deniedTools = [...params.deniedTools || [], ...ALWAYS_DENIED_FOR_AGENTS, "create_dynamic_agent"];
18923
- const agentDefinition = {
18924
- description: params.description || `Dynamic agent: ${params.name}`,
18925
- model: params.model || DEFAULT_AGENT_MODEL,
18926
- systemPrompt: params.systemPrompt,
18927
- allowedTools: params.allowedTools,
18928
- deniedTools,
18929
- maxIterations: { ...DEFAULT_MAX_ITERATIONS },
18930
- defaultThoroughness: DEFAULT_THOROUGHNESS,
18931
- defaultVariables: params.variables
18932
- };
18933
- const spawnOptions = {
18934
- task: params.task,
18935
- agentName: params.name,
18936
- thoroughness: params.thoroughness,
18937
- variables: params.variables,
18938
- parentSessionId,
18939
- model: params.model,
18940
- allowedTools: params.allowedTools,
18941
- agentDefinition
18942
- };
18943
- if (params.run_in_background && backgroundManager) {
18944
- const jobId = backgroundManager.spawn({
18945
- ...spawnOptions,
18946
- groupDescription: params.group_description
18947
- });
18948
- return `Dynamic background agent "${params.name}" started. Job ID: ${jobId}. Use check_agent_status tool with this job ID to retrieve results when ready.`;
18949
- }
18950
- const result = await orchestrator.delegateToAgent(spawnOptions);
18951
- return result.summary;
18952
- },
18953
- toolSchema: {
18954
- name: "create_dynamic_agent",
18955
- description: `Create and spawn a one-off agent at runtime with a custom system prompt.
18956
-
18957
- Unlike agent_delegate (which uses pre-defined agents), this tool lets you compose a new agent on the fly with custom instructions, model, and tool restrictions.
18958
-
18959
- **When to use this tool:**
18960
- - When no existing agent fits the task at hand
18961
- - When you need a specialized agent with custom instructions for a one-off task
18962
- - When you need fine-grained control over what tools the agent can access
18963
-
18964
- **Constraints:**
18965
- - Dynamic agents CANNOT call agent_delegate or create_dynamic_agent (no recursive spawning)
18966
- - Dynamic agents are ephemeral \u2014 they are not saved or reusable across sessions
18967
-
18968
- **Example uses:**
18969
- - Create a security auditor agent with specific review criteria
18970
- - Create a migration agent that follows a custom checklist
18971
- - Create a specialized code generator with domain-specific instructions`,
18972
- parameters: {
18973
- type: "object",
18974
- properties: {
18975
- task: {
18976
- type: "string",
18977
- description: "Clear description of what you want the dynamic agent to accomplish."
18978
- },
18979
- name: {
18980
- type: "string",
18981
- description: 'Unique name for this dynamic agent (e.g., "security-auditor", "migration-helper"). Used for logging and identification.'
18982
- },
18983
- systemPrompt: {
18984
- type: "string",
18985
- description: "Custom system prompt for the agent. This defines the agent's role, capabilities, and constraints. Use $TASK to reference the task parameter."
18986
- },
18987
- description: {
18988
- type: "string",
18989
- description: "Short description of the agent's purpose (for logging)."
18990
- },
18991
- model: {
18992
- type: "string",
18993
- description: 'Model to use for this agent (e.g., "claude-sonnet-4-5-20250929"). Defaults to the agent system default.'
18994
- },
18995
- allowedTools: {
18996
- type: "array",
18997
- items: { type: "string" },
18998
- description: 'Whitelist of tool name patterns the agent can use. Supports wildcards (e.g., "file_*", "mcp__github__*"). If omitted, all non-denied tools are available.'
18999
- },
19000
- deniedTools: {
19001
- type: "array",
19002
- items: { type: "string" },
19003
- description: "Blacklist of tool name patterns the agent cannot use. agent_delegate and create_dynamic_agent are always denied."
19004
- },
19005
- thoroughness: {
19006
- type: "string",
19007
- enum: ["quick", "medium", "very_thorough"],
19008
- description: `How thoroughly to execute:
19009
- - quick: Fast, 1-2 iterations
19010
- - medium: Balanced, 3-5 iterations (default)
19011
- - very_thorough: Comprehensive, 8-10+ iterations`
19012
- },
19013
- variables: {
19014
- type: "object",
19015
- additionalProperties: { type: "string" },
19016
- description: 'Variables to substitute in the system prompt. For example: { "DOMAIN": "auth" } replaces $DOMAIN in the prompt.'
19017
- },
19018
- run_in_background: {
19019
- type: "boolean",
19020
- description: "Run the agent in the background (non-blocking). Returns a job ID immediately. Use check_agent_status to poll for results."
19021
- },
19022
- group_description: {
19023
- type: "string",
19024
- description: "Short description of what this group of background agents is working on. Only needed for the first background agent in a group."
19025
- }
19026
- },
19027
- required: ["task", "name", "systemPrompt"]
19028
- }
19029
- }
19030
- };
19031
- }
19032
-
19033
19260
  // src/agents/BackgroundAgentManager.ts
19034
19261
  import { randomBytes as randomBytes4 } from "crypto";
19035
19262
  var DEFAULT_MAX_CONCURRENT = 4;
@@ -19550,7 +19777,7 @@ function createTodoStore(onUpdate) {
19550
19777
 
19551
19778
  // src/tools/findDefinitionTool.ts
19552
19779
  import { stat as stat3 } from "fs/promises";
19553
- import path17 from "path";
19780
+ import path19 from "path";
19554
19781
  import { execFile as execFile2 } from "child_process";
19555
19782
  import { promisify as promisify2 } from "util";
19556
19783
  import { createRequire as createRequire2 } from "module";
@@ -19571,8 +19798,8 @@ var ALL_KEYWORDS = Object.values(KIND_KEYWORDS).flat();
19571
19798
  function getRipgrepPath2() {
19572
19799
  try {
19573
19800
  const ripgrepPath = require3.resolve("@vscode/ripgrep");
19574
- const ripgrepDir = path17.dirname(ripgrepPath);
19575
- return path17.join(ripgrepDir, "..", "bin", "rg" + (process.platform === "win32" ? ".exe" : ""));
19801
+ const ripgrepDir = path19.dirname(ripgrepPath);
19802
+ return path19.join(ripgrepDir, "..", "bin", "rg" + (process.platform === "win32" ? ".exe" : ""));
19576
19803
  } catch {
19577
19804
  throw new Error(
19578
19805
  "ripgrep is not available. Please install @vscode/ripgrep: pnpm add @vscode/ripgrep --filter @bike4mind/services"
@@ -19580,10 +19807,10 @@ function getRipgrepPath2() {
19580
19807
  }
19581
19808
  }
19582
19809
  function isPathWithinWorkspace2(targetPath, baseCwd) {
19583
- const resolvedTarget = path17.resolve(targetPath);
19584
- const resolvedBase = path17.resolve(baseCwd);
19585
- const relativePath = path17.relative(resolvedBase, resolvedTarget);
19586
- return !relativePath.startsWith("..") && !path17.isAbsolute(relativePath);
19810
+ const resolvedTarget = path19.resolve(targetPath);
19811
+ const resolvedBase = path19.resolve(baseCwd);
19812
+ const relativePath = path19.relative(resolvedBase, resolvedTarget);
19813
+ return !relativePath.startsWith("..") && !path19.isAbsolute(relativePath);
19587
19814
  }
19588
19815
  function escapeRegex(str) {
19589
19816
  return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
@@ -19616,7 +19843,7 @@ async function findDefinitions(params) {
19616
19843
  throw new Error("symbol_name is required");
19617
19844
  }
19618
19845
  const baseCwd = process.cwd();
19619
- const targetDir = search_path ? path17.resolve(baseCwd, search_path) : baseCwd;
19846
+ const targetDir = search_path ? path19.resolve(baseCwd, search_path) : baseCwd;
19620
19847
  if (!isPathWithinWorkspace2(targetDir, baseCwd)) {
19621
19848
  throw new Error(`Path validation failed: "${search_path}" resolves outside the allowed workspace directory`);
19622
19849
  }
@@ -19671,7 +19898,7 @@ async function findDefinitions(params) {
19671
19898
  const lineText = match.data.lines.text.trimEnd();
19672
19899
  if (isLikelyDefinition(lineText)) {
19673
19900
  allMatches.push({
19674
- filePath: path17.relative(targetDir, match.data.path.text) || path17.basename(match.data.path.text),
19901
+ filePath: path19.relative(targetDir, match.data.path.text) || path19.basename(match.data.path.text),
19675
19902
  lineNumber: match.data.line_number,
19676
19903
  line: lineText
19677
19904
  });
@@ -19749,8 +19976,8 @@ function createFindDefinitionTool() {
19749
19976
  }
19750
19977
 
19751
19978
  // src/tools/getFileStructure/index.ts
19752
- import { existsSync as existsSync10, promises as fs13, statSync as statSync6 } from "fs";
19753
- import path18 from "path";
19979
+ import { existsSync as existsSync12, promises as fs14, statSync as statSync6 } from "fs";
19980
+ import path20 from "path";
19754
19981
 
19755
19982
  // src/tools/getFileStructure/formatter.ts
19756
19983
  var SECTIONS = [
@@ -19787,35 +20014,35 @@ function formatSection(lines, title, items, format) {
19787
20014
  }
19788
20015
 
19789
20016
  // src/tools/getFileStructure/index.ts
19790
- var MAX_FILE_SIZE3 = 10 * 1024 * 1024;
20017
+ var MAX_FILE_SIZE4 = 10 * 1024 * 1024;
19791
20018
  function createGetFileStructureTool() {
19792
20019
  return {
19793
20020
  toolFn: async (value) => {
19794
20021
  const params = value;
19795
20022
  try {
19796
20023
  const cwd = process.cwd();
19797
- const resolvedPath = path18.resolve(cwd, params.path);
20024
+ const resolvedPath = path20.resolve(cwd, params.path);
19798
20025
  if (!resolvedPath.startsWith(cwd)) {
19799
20026
  return "Error: Access denied - cannot read files outside of current working directory";
19800
20027
  }
19801
- if (!existsSync10(resolvedPath)) {
20028
+ if (!existsSync12(resolvedPath)) {
19802
20029
  return `Error: File not found: ${params.path}`;
19803
20030
  }
19804
20031
  const stats = statSync6(resolvedPath);
19805
20032
  if (stats.isDirectory()) {
19806
20033
  return `Error: Path is a directory, not a file: ${params.path}`;
19807
20034
  }
19808
- if (stats.size > MAX_FILE_SIZE3) {
19809
- return `Error: File too large (${(stats.size / 1024 / 1024).toFixed(2)}MB). Max: ${MAX_FILE_SIZE3 / 1024 / 1024}MB`;
20035
+ if (stats.size > MAX_FILE_SIZE4) {
20036
+ return `Error: File too large (${(stats.size / 1024 / 1024).toFixed(2)}MB). Max: ${MAX_FILE_SIZE4 / 1024 / 1024}MB`;
19810
20037
  }
19811
- const ext = path18.extname(resolvedPath).toLowerCase();
20038
+ const ext = path20.extname(resolvedPath).toLowerCase();
19812
20039
  const { getLanguageForExtension, parseFileStructure, getSupportedLanguages } = await import("./treeSitterEngine-4SGFQDY3.js");
19813
20040
  const languageId = getLanguageForExtension(ext);
19814
20041
  if (!languageId) {
19815
20042
  const supported = getSupportedLanguages();
19816
20043
  return `Error: Unsupported file type "${ext}". Supported languages: ${supported.join(", ")}`;
19817
20044
  }
19818
- const sourceCode = await fs13.readFile(resolvedPath, "utf-8");
20045
+ const sourceCode = await fs14.readFile(resolvedPath, "utf-8");
19819
20046
  const lineCount = sourceCode.split("\n").length;
19820
20047
  const items = await parseFileStructure(sourceCode, languageId);
19821
20048
  return formatStructureOutput(params.path, items, stats.size, lineCount);
@@ -19877,7 +20104,8 @@ function CliApp() {
19877
20104
  abortController: null,
19878
20105
  contextContent: "",
19879
20106
  backgroundManager: null,
19880
- wsManager: null
20107
+ wsManager: null,
20108
+ checkpointStore: null
19881
20109
  });
19882
20110
  const [isInitialized, setIsInitialized] = useState10(false);
19883
20111
  const [initError, setInitError] = useState10(null);
@@ -20131,6 +20359,12 @@ function CliApp() {
20131
20359
  enqueuePermissionPrompt(prompt);
20132
20360
  });
20133
20361
  };
20362
+ const checkpointProjectDir = state.configStore.getProjectConfigDir() || process.cwd();
20363
+ const checkpointStore = new CheckpointStore(checkpointProjectDir);
20364
+ try {
20365
+ await checkpointStore.init(newSession.id);
20366
+ } catch {
20367
+ }
20134
20368
  const agentContext = {
20135
20369
  currentAgent: null,
20136
20370
  observationQueue: []
@@ -20143,7 +20377,10 @@ function CliApp() {
20143
20377
  promptFn,
20144
20378
  agentContext,
20145
20379
  state.configStore,
20146
- apiClient
20380
+ apiClient,
20381
+ void 0,
20382
+ // toolFilter
20383
+ checkpointStore
20147
20384
  );
20148
20385
  startupLog.push(`\u{1F6E0}\uFE0F Loaded ${b4mTools2.length} B4M tool(s)`);
20149
20386
  const mcpManager = new McpManager(config);
@@ -20174,7 +20411,8 @@ function CliApp() {
20174
20411
  apiClient,
20175
20412
  agentStore,
20176
20413
  customCommandStore: state.customCommandStore,
20177
- enableParallelToolExecution: config.preferences.enableParallelToolExecution === true
20414
+ enableParallelToolExecution: config.preferences.enableParallelToolExecution === true,
20415
+ checkpointStore
20178
20416
  });
20179
20417
  const backgroundManager = new BackgroundAgentManager(orchestrator);
20180
20418
  backgroundManager.setOnStatusChange((job) => {
@@ -20185,7 +20423,6 @@ function CliApp() {
20185
20423
  useCliStore.getState().setPendingBackgroundTrigger(true);
20186
20424
  });
20187
20425
  const agentDelegateTool = createAgentDelegateTool(orchestrator, agentStore, newSession.id, backgroundManager);
20188
- const dynamicAgentTool = config.preferences.enableDynamicAgentCreation === true ? createDynamicAgentTool(orchestrator, newSession.id, backgroundManager) : null;
20189
20426
  const backgroundTools = createBackgroundAgentTools(backgroundManager);
20190
20427
  const notifyingLlm = new NotifyingLlmBackend(llm, backgroundManager);
20191
20428
  const todoStore = createTodoStore();
@@ -20208,9 +20445,6 @@ function CliApp() {
20208
20445
  if (skillTool) {
20209
20446
  cliTools.push(skillTool);
20210
20447
  }
20211
- if (dynamicAgentTool) {
20212
- cliTools.push(dynamicAgentTool);
20213
- }
20214
20448
  const allTools = [...b4mTools2, ...mcpTools, ...cliTools];
20215
20449
  startupLog.push(`\u{1F4C2} Working directory: ${process.cwd()}`);
20216
20450
  const agentNamesList = agentStore.getAgentNames().join(", ");
@@ -20221,9 +20455,6 @@ function CliApp() {
20221
20455
  startupLog.push(`\u{1F6E0}\uFE0F Skill tool enabled (${skillCount} skills available)`);
20222
20456
  }
20223
20457
  }
20224
- if (dynamicAgentTool) {
20225
- startupLog.push(`\u{1F9EA} Dynamic agent creation enabled (experimental)`);
20226
- }
20227
20458
  logger.debug(
20228
20459
  `Total tools available to agent: ${allTools.length} (${b4mTools2.length} B4M + ${mcpTools.length} MCP + ${cliTools.length} CLI)`
20229
20460
  );
@@ -20242,8 +20473,7 @@ function CliApp() {
20242
20473
  contextContent: contextResult.mergedContent,
20243
20474
  agentStore,
20244
20475
  customCommands: state.customCommandStore.getAllCommands(),
20245
- enableSkillTool,
20246
- enableDynamicAgentCreation: config.preferences.enableDynamicAgentCreation === true
20476
+ enableSkillTool
20247
20477
  });
20248
20478
  const maxIterations = config.preferences.maxIterations === null ? 999999 : config.preferences.maxIterations;
20249
20479
  const agent = new ReActAgent({
@@ -20305,8 +20535,10 @@ function CliApp() {
20305
20535
  // Store raw context for compact instructions
20306
20536
  backgroundManager,
20307
20537
  // Store for grouped notification turn tracking
20308
- wsManager
20538
+ wsManager,
20309
20539
  // WebSocket connection manager (null if using SSE fallback)
20540
+ checkpointStore
20541
+ // File change checkpointing for undo/restore
20310
20542
  }));
20311
20543
  setStoreSession(newSession);
20312
20544
  const bannerLines = [
@@ -20999,6 +21231,10 @@ Available commands:
20999
21231
  /exit - Exit the CLI
21000
21232
  /clear - Start a new session
21001
21233
  /rewind - Rewind conversation to a previous point
21234
+ /undo - Undo the last file change
21235
+ /checkpoints - List available file restore points
21236
+ /restore <n> - Restore files to a specific checkpoint
21237
+ /diff [n] - Show diff between current state and a checkpoint
21002
21238
  /login - Authenticate with your B4M account
21003
21239
  /logout - Clear authentication and sign out
21004
21240
  /whoami - Show current authenticated user
@@ -21091,6 +21327,9 @@ Keyboard Shortcuts:
21091
21327
  }
21092
21328
  await logger.initialize(loadedSession.id);
21093
21329
  logger.debug("=== Session Resumed ===");
21330
+ if (state.checkpointStore) {
21331
+ state.checkpointStore.setSessionId(loadedSession.id);
21332
+ }
21094
21333
  setState((prev) => ({ ...prev, session: loadedSession }));
21095
21334
  setStoreSession(loadedSession);
21096
21335
  useCliStore.getState().clearPendingMessages();
@@ -21329,6 +21568,9 @@ Keyboard Shortcuts:
21329
21568
  };
21330
21569
  await logger.initialize(newSession.id);
21331
21570
  logger.debug("=== New Session Started via /clear ===");
21571
+ if (state.checkpointStore) {
21572
+ state.checkpointStore.setSessionId(newSession.id);
21573
+ }
21332
21574
  setState((prev) => ({ ...prev, session: newSession }));
21333
21575
  setStoreSession(newSession);
21334
21576
  useCliStore.getState().clearPendingMessages();
@@ -21339,8 +21581,7 @@ Keyboard Shortcuts:
21339
21581
  `);
21340
21582
  break;
21341
21583
  }
21342
- case "rewind":
21343
- case "undo": {
21584
+ case "rewind": {
21344
21585
  if (!state.session) {
21345
21586
  console.log("No active session to rewind");
21346
21587
  return;
@@ -21399,6 +21640,103 @@ Keyboard Shortcuts:
21399
21640
  }));
21400
21641
  break;
21401
21642
  }
21643
+ case "undo": {
21644
+ if (!state.checkpointStore) {
21645
+ console.log("Checkpointing not available.");
21646
+ return;
21647
+ }
21648
+ const undoCheckpoints = state.checkpointStore.listCheckpoints();
21649
+ if (undoCheckpoints.length === 0) {
21650
+ console.log("No checkpoints available. No file changes have been made yet.");
21651
+ return;
21652
+ }
21653
+ try {
21654
+ const restored = await state.checkpointStore.undoLast();
21655
+ console.log(`
21656
+ \u2705 Restored ${restored.filePaths.length} file(s) to state before: ${restored.name}`);
21657
+ for (const f of restored.filePaths) {
21658
+ console.log(` - ${f}`);
21659
+ }
21660
+ console.log("");
21661
+ } catch (err) {
21662
+ console.log(`Failed to undo: ${err instanceof Error ? err.message : String(err)}`);
21663
+ }
21664
+ break;
21665
+ }
21666
+ case "checkpoints": {
21667
+ if (!state.checkpointStore) {
21668
+ console.log("Checkpointing not available.");
21669
+ return;
21670
+ }
21671
+ const cpList = state.checkpointStore.listCheckpoints();
21672
+ if (cpList.length === 0) {
21673
+ console.log("No checkpoints yet. Checkpoints are created automatically before file changes.");
21674
+ return;
21675
+ }
21676
+ console.log("\nCheckpoints (most recent first):\n");
21677
+ cpList.forEach((cp, idx) => {
21678
+ const ageMs = Date.now() - new Date(cp.timestamp).getTime();
21679
+ const ageSec = Math.floor(ageMs / 1e3);
21680
+ let age;
21681
+ if (ageSec < 60) age = `${ageSec}s ago`;
21682
+ else if (ageSec < 3600) age = `${Math.floor(ageSec / 60)}m ago`;
21683
+ else age = `${Math.floor(ageSec / 3600)}h ago`;
21684
+ console.log(` ${idx + 1}. ${cp.name} (${age}) - ${cp.filePaths.length} file(s)`);
21685
+ });
21686
+ console.log("\nUse /restore <number> to restore, /diff <number> to see changes.\n");
21687
+ break;
21688
+ }
21689
+ case "restore": {
21690
+ if (!state.checkpointStore) {
21691
+ console.log("Checkpointing not available.");
21692
+ return;
21693
+ }
21694
+ const restoreTarget = args[0];
21695
+ if (!restoreTarget) {
21696
+ console.log("Usage: /restore <checkpoint-number>");
21697
+ console.log("Use /checkpoints to list available restore points.");
21698
+ return;
21699
+ }
21700
+ const restoreNum = parseInt(restoreTarget, 10);
21701
+ if (isNaN(restoreNum) || restoreNum < 1) {
21702
+ console.log("Please provide a valid checkpoint number. Use /checkpoints to list.");
21703
+ return;
21704
+ }
21705
+ try {
21706
+ const restoredCp = await state.checkpointStore.restoreCheckpoint(restoreNum);
21707
+ console.log(`
21708
+ \u2705 Restored to checkpoint: ${restoredCp.name}`);
21709
+ for (const f of restoredCp.filePaths) {
21710
+ console.log(` - ${f}`);
21711
+ }
21712
+ console.log("");
21713
+ } catch (err) {
21714
+ console.log(`Failed to restore: ${err instanceof Error ? err.message : String(err)}`);
21715
+ }
21716
+ break;
21717
+ }
21718
+ case "diff": {
21719
+ if (!state.checkpointStore) {
21720
+ console.log("Checkpointing not available.");
21721
+ return;
21722
+ }
21723
+ const diffTarget = args[0] ? parseInt(args[0], 10) : 1;
21724
+ if (isNaN(diffTarget) || diffTarget < 1) {
21725
+ console.log("Usage: /diff [checkpoint-number] (defaults to 1, most recent)");
21726
+ return;
21727
+ }
21728
+ try {
21729
+ const diffOutput = state.checkpointStore.getCheckpointDiff(diffTarget);
21730
+ if (!diffOutput.trim()) {
21731
+ console.log("No differences found between checkpoint and current state.");
21732
+ } else {
21733
+ console.log(diffOutput);
21734
+ }
21735
+ } catch (err) {
21736
+ console.log(`Failed to generate diff: ${err instanceof Error ? err.message : String(err)}`);
21737
+ }
21738
+ break;
21739
+ }
21402
21740
  case "usage": {
21403
21741
  const usageAuthTokens = await state.configStore.getAuthTokens();
21404
21742
  if (!usageAuthTokens || new Date(usageAuthTokens.expiresAt) <= /* @__PURE__ */ new Date()) {