@bike4mind/cli 0.11.0 → 0.12.1
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/README.md +29 -0
- package/bin/bike4mind-cli.mjs +57 -0
- package/dist/{ConfigStore-Bku1XvrD.mjs → ConfigStore-C3tokQej.mjs} +103 -4
- package/dist/commands/apiCommand.mjs +1 -1
- package/dist/commands/doctorCommand.mjs +1 -1
- package/dist/commands/envCommand.mjs +25 -0
- package/dist/commands/headlessCommand.mjs +2 -2
- package/dist/commands/mcpCommand.mjs +1 -1
- package/dist/commands/updateCommand.mjs +1 -1
- package/dist/index.mjs +40 -9
- package/dist/{package-CVajrVMD.mjs → package-CMkVxGGC.mjs} +1 -1
- package/dist/{tools-Ddni4Pbz.mjs → tools-QQ6ibgPF.mjs} +126 -31
- package/package.json +8 -8
package/README.md
CHANGED
|
@@ -74,6 +74,8 @@ b4m [options]
|
|
|
74
74
|
```
|
|
75
75
|
|
|
76
76
|
**Available flags:**
|
|
77
|
+
- `--dev` - Point the CLI at the local dev server (`http://localhost:3001`) and remember it
|
|
78
|
+
- `--prod` - Point the CLI at Bike4Mind production and remember it
|
|
77
79
|
- `--verbose`, `-v` - Show debug logs in console (useful for troubleshooting)
|
|
78
80
|
- `--help`, `-h` - Show help information
|
|
79
81
|
- `--version`, `-V` - Show CLI version
|
|
@@ -90,6 +92,28 @@ b4m --version
|
|
|
90
92
|
b4m --help
|
|
91
93
|
```
|
|
92
94
|
|
|
95
|
+
### Switching environments (`--dev` / `--prod`)
|
|
96
|
+
|
|
97
|
+
Flip which backend the CLI talks to without editing config by hand:
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
b4m --dev # local dev server (http://localhost:3001)
|
|
101
|
+
b4m --prod # Bike4Mind production
|
|
102
|
+
b4m # reuses whichever environment you last selected (sticky)
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
The choice is persisted to `~/.bike4mind/config.json`, so a bare `b4m` always
|
|
106
|
+
reopens the environment you last chose. Both single- and double-dash forms work
|
|
107
|
+
(`-dev`/`--dev`, `-prod`/`--prod`); `--local` is an alias for `--dev`.
|
|
108
|
+
|
|
109
|
+
Auth tokens are cached **per environment**, so switching back and forth does not
|
|
110
|
+
force a re-login — each environment remembers its own session. The first time you
|
|
111
|
+
visit a new environment you'll be prompted to `/login`. The active environment is
|
|
112
|
+
shown in the startup banner (`🌍 API Environment: …`).
|
|
113
|
+
|
|
114
|
+
> For a one-off custom/self-hosted URL, use the in-session `/set-api <url>` command
|
|
115
|
+
> (see [API Configuration](#api-configuration) below).
|
|
116
|
+
|
|
93
117
|
## Commands
|
|
94
118
|
|
|
95
119
|
While in interactive mode:
|
|
@@ -137,6 +161,11 @@ Authentication tokens are securely stored in your config file with restricted pe
|
|
|
137
161
|
|
|
138
162
|
By default, the CLI connects to the main Bike4Mind service at `https://app.bike4mind.com`.
|
|
139
163
|
|
|
164
|
+
**Quick switch between local dev and production:** use the `b4m --dev` / `b4m --prod`
|
|
165
|
+
launch flags (see [Switching environments](#switching-environments---dev----prod)).
|
|
166
|
+
They persist your choice and cache auth per-environment. The `/set-api`, `/reset-api`,
|
|
167
|
+
and `/api-info` commands below operate on the same setting from inside a session.
|
|
168
|
+
|
|
140
169
|
**For Self-Hosted Instances:**
|
|
141
170
|
|
|
142
171
|
If your organization runs a self-hosted Bike4Mind instance, connect to it using:
|
package/bin/bike4mind-cli.mjs
CHANGED
|
@@ -33,8 +33,43 @@ const require = createRequire(import.meta.url);
|
|
|
33
33
|
// whichever package.json it discovers first, which is not necessarily ours.
|
|
34
34
|
const { version: cliVersion } = require('../package.json');
|
|
35
35
|
|
|
36
|
+
// --- API environment flags (--dev / --prod) ---
|
|
37
|
+
// Intercept these BEFORE yargs parses argv. They're accepted with either a
|
|
38
|
+
// single or double dash (e.g. `b4m -prod` and `b4m --prod` both work), but
|
|
39
|
+
// single-dash multi-char tokens would otherwise be split into clustered short
|
|
40
|
+
// flags by yargs (`-prod` → `-p -r -o -d`, colliding with -p/--prompt). We pull
|
|
41
|
+
// them out here, record the target, and strip them so yargs sees a clean argv.
|
|
42
|
+
const ENV_FLAG_MAP = {
|
|
43
|
+
'--dev': 'dev', '-dev': 'dev', '--local': 'dev', '-local': 'dev',
|
|
44
|
+
'--prod': 'prod', '-prod': 'prod', '--production': 'prod', '-production': 'prod',
|
|
45
|
+
};
|
|
46
|
+
let envTarget = null;
|
|
47
|
+
{
|
|
48
|
+
const cleaned = [];
|
|
49
|
+
for (const token of process.argv.slice(2)) {
|
|
50
|
+
if (Object.prototype.hasOwnProperty.call(ENV_FLAG_MAP, token)) {
|
|
51
|
+
envTarget = ENV_FLAG_MAP[token]; // last one wins
|
|
52
|
+
} else {
|
|
53
|
+
cleaned.push(token);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
// Rebuild argv without the env tokens so yargs doesn't choke on them.
|
|
57
|
+
process.argv = [process.argv[0], process.argv[1], ...cleaned];
|
|
58
|
+
}
|
|
59
|
+
|
|
36
60
|
// Parse CLI arguments
|
|
37
61
|
const argv = await yargs(hideBin(process.argv))
|
|
62
|
+
// --dev / --prod are declared here ONLY so they appear in `--help`. The actual
|
|
63
|
+
// handling is the pre-yargs argv interception above — these yargs-side values
|
|
64
|
+
// (`argv.dev` / `argv.prod`) are never read.
|
|
65
|
+
.option('dev', {
|
|
66
|
+
type: 'boolean',
|
|
67
|
+
description: 'Point the CLI at the local dev server (http://localhost:3001) and remember it',
|
|
68
|
+
})
|
|
69
|
+
.option('prod', {
|
|
70
|
+
type: 'boolean',
|
|
71
|
+
description: 'Point the CLI at Bike4Mind production and remember it',
|
|
72
|
+
})
|
|
38
73
|
.option('verbose', {
|
|
39
74
|
alias: 'v',
|
|
40
75
|
type: 'boolean',
|
|
@@ -204,6 +239,28 @@ if (argv['reset-api'] || argv['api-url'] !== undefined) {
|
|
|
204
239
|
}
|
|
205
240
|
}
|
|
206
241
|
|
|
242
|
+
// Apply --dev / --prod environment switch before anything connects to a server.
|
|
243
|
+
// This persists the choice (sticky: a bare `b4m` reuses the last selection) and
|
|
244
|
+
// swaps in that environment's cached auth token.
|
|
245
|
+
if (envTarget) {
|
|
246
|
+
try {
|
|
247
|
+
let applyEnvironmentFlag;
|
|
248
|
+
|
|
249
|
+
if (isDev) {
|
|
250
|
+
const { register } = require('tsx/esm/api');
|
|
251
|
+
register();
|
|
252
|
+
({ applyEnvironmentFlag } = await import('../src/commands/envCommand.ts'));
|
|
253
|
+
} else {
|
|
254
|
+
({ applyEnvironmentFlag } = await import('../dist/commands/envCommand.mjs'));
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
await applyEnvironmentFlag(envTarget);
|
|
258
|
+
} catch (error) {
|
|
259
|
+
console.error('Failed to switch API environment:', error.message);
|
|
260
|
+
process.exit(1);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
207
264
|
// Handle headless mode (-p / --prompt flag)
|
|
208
265
|
// Must be done after isDev detection to use correct import path
|
|
209
266
|
if (argv.prompt !== undefined) {
|
|
@@ -2597,7 +2597,8 @@ const AgentStepSchema = z.object({
|
|
|
2597
2597
|
});
|
|
2598
2598
|
const ExecutionStartedAction = z.object({
|
|
2599
2599
|
action: z.literal("execution_started"),
|
|
2600
|
-
executionId: z.string()
|
|
2600
|
+
executionId: z.string(),
|
|
2601
|
+
questId: z.string().optional()
|
|
2601
2602
|
});
|
|
2602
2603
|
const IterationStepAction = z.object({
|
|
2603
2604
|
action: z.literal("iteration_step"),
|
|
@@ -4978,7 +4979,8 @@ const settingsMap = {
|
|
|
4978
4979
|
description: "The default AI model to use for API requests when no model is specified.",
|
|
4979
4980
|
options: CHAT_MODELS,
|
|
4980
4981
|
category: "AI",
|
|
4981
|
-
order: 1
|
|
4982
|
+
order: 1,
|
|
4983
|
+
publicSafe: true
|
|
4982
4984
|
}),
|
|
4983
4985
|
openaiDemoKey: makeStringSetting({
|
|
4984
4986
|
key: "openaiDemoKey",
|
|
@@ -5943,7 +5945,8 @@ const settingsMap = {
|
|
|
5943
5945
|
name: "Enforce Multi-Factor Authentication",
|
|
5944
5946
|
description: "Require TOTP (Time-based One-Time Password) for all users when logging in. When disabled, MFA is optional and users can enable/disable it in their profiles.",
|
|
5945
5947
|
defaultValue: false,
|
|
5946
|
-
category: "Users"
|
|
5948
|
+
category: "Users",
|
|
5949
|
+
publicSafe: true
|
|
5947
5950
|
}),
|
|
5948
5951
|
FirecrawlApiKey: makeStringSetting({
|
|
5949
5952
|
key: "FirecrawlApiKey",
|
|
@@ -9645,6 +9648,29 @@ dayjs.extend(timezone);
|
|
|
9645
9648
|
dayjs.extend(relativeTime);
|
|
9646
9649
|
dayjs.extend(localizedFormat);
|
|
9647
9650
|
var dayjsConfig_default = dayjs;
|
|
9651
|
+
//#endregion
|
|
9652
|
+
//#region src/utils/apiUrl.ts
|
|
9653
|
+
/** Bike4Mind production service (used when no customUrl is configured). */
|
|
9654
|
+
const BIKE4MIND_URL = "https://app.bike4mind.com";
|
|
9655
|
+
/** Local development server the `--dev` flag points the CLI at. */
|
|
9656
|
+
const LOCAL_DEV_URL = "http://localhost:3001";
|
|
9657
|
+
/**
|
|
9658
|
+
* Resolve API URL based on configuration
|
|
9659
|
+
* Returns custom URL if set, otherwise Bike4Mind main service
|
|
9660
|
+
*/
|
|
9661
|
+
function getApiUrl(configApiConfig) {
|
|
9662
|
+
if (configApiConfig?.customUrl) return configApiConfig.customUrl;
|
|
9663
|
+
return BIKE4MIND_URL;
|
|
9664
|
+
}
|
|
9665
|
+
/**
|
|
9666
|
+
* Get human-readable API type name
|
|
9667
|
+
*/
|
|
9668
|
+
function getEnvironmentName(configApiConfig) {
|
|
9669
|
+
const url = configApiConfig?.customUrl;
|
|
9670
|
+
if (!url) return "Production";
|
|
9671
|
+
if (/^https?:\/\/(localhost|127\.0\.0\.1)(:|\/|$)/i.test(url)) return "Local Dev";
|
|
9672
|
+
return "Self-Hosted";
|
|
9673
|
+
}
|
|
9648
9674
|
const logger = class Logger {
|
|
9649
9675
|
static {
|
|
9650
9676
|
this.instance = null;
|
|
@@ -9986,6 +10012,7 @@ const CliConfigSchema = z.object({
|
|
|
9986
10012
|
version: z.string(),
|
|
9987
10013
|
userId: z.string(),
|
|
9988
10014
|
auth: AuthTokensSchema.optional(),
|
|
10015
|
+
authByEnv: z.record(z.string(), AuthTokensSchema).optional(),
|
|
9989
10016
|
defaultModel: z.string(),
|
|
9990
10017
|
apiConfig: ApiConfigSchema.optional(),
|
|
9991
10018
|
toolApiKeys: z.object({
|
|
@@ -10277,6 +10304,26 @@ function mergeConfigs(global, project, local) {
|
|
|
10277
10304
|
return merged;
|
|
10278
10305
|
}
|
|
10279
10306
|
/**
|
|
10307
|
+
* Normalize an API URL for use as an `authByEnv` cache key.
|
|
10308
|
+
*
|
|
10309
|
+
* Without normalization, `/set-api https://x.com` and `/set-api https://x.com/`
|
|
10310
|
+
* (or `HTTPS://X.com`) would create separate cache entries, defeating the
|
|
10311
|
+
* per-environment token reuse on a later `--dev` / `--prod` switch.
|
|
10312
|
+
*/
|
|
10313
|
+
function normalizeEnvKey(url) {
|
|
10314
|
+
return url.toLowerCase().replace(/\/+$/, "");
|
|
10315
|
+
}
|
|
10316
|
+
/**
|
|
10317
|
+
* Treat an auth token as "authenticated" only when it has an `expiresAt` in
|
|
10318
|
+
* the future. The startup flow auto-refreshes expired tokens anyway, but
|
|
10319
|
+
* without this check the launch banner would briefly claim a saved login is
|
|
10320
|
+
* being reused when it's actually about to trigger a re-auth.
|
|
10321
|
+
*/
|
|
10322
|
+
function hasValidAuth(auth) {
|
|
10323
|
+
if (!auth) return false;
|
|
10324
|
+
return new Date(auth.expiresAt) > /* @__PURE__ */ new Date();
|
|
10325
|
+
}
|
|
10326
|
+
/**
|
|
10280
10327
|
* Manages CLI configuration stored as JSON
|
|
10281
10328
|
*/
|
|
10282
10329
|
var ConfigStore = class {
|
|
@@ -10544,6 +10591,58 @@ var ConfigStore = class {
|
|
|
10544
10591
|
await this.save(config);
|
|
10545
10592
|
}
|
|
10546
10593
|
/**
|
|
10594
|
+
* Switch the active API environment, caching auth tokens per-environment so
|
|
10595
|
+
* flipping between `--dev` and `--prod` doesn't force a re-login each time you
|
|
10596
|
+
* return to an environment you've already authenticated.
|
|
10597
|
+
*
|
|
10598
|
+
* Targets:
|
|
10599
|
+
* - 'prod' → Bike4Mind production (clears customUrl)
|
|
10600
|
+
* - 'dev' → local dev server (http://localhost:3001)
|
|
10601
|
+
* - { customUrl: '…' } → arbitrary self-hosted URL
|
|
10602
|
+
*
|
|
10603
|
+
* Mutates the cached config in place and persists via `save()` (no argument)
|
|
10604
|
+
* so the write bypasses save()'s field-merge — `save(config)` would otherwise
|
|
10605
|
+
* preserve the previous `auth` and defeat the per-env swap.
|
|
10606
|
+
*/
|
|
10607
|
+
async switchApiEnvironment(target) {
|
|
10608
|
+
const config = await this.load();
|
|
10609
|
+
const prevKey = normalizeEnvKey(config.apiConfig?.customUrl || "https://app.bike4mind.com");
|
|
10610
|
+
let newUrl;
|
|
10611
|
+
let newApiConfig;
|
|
10612
|
+
if (target === "prod") {
|
|
10613
|
+
newUrl = BIKE4MIND_URL;
|
|
10614
|
+
newApiConfig = void 0;
|
|
10615
|
+
} else if (target === "dev") {
|
|
10616
|
+
newUrl = LOCAL_DEV_URL;
|
|
10617
|
+
newApiConfig = { customUrl: LOCAL_DEV_URL };
|
|
10618
|
+
} else {
|
|
10619
|
+
newUrl = target.customUrl;
|
|
10620
|
+
newApiConfig = { customUrl: target.customUrl };
|
|
10621
|
+
}
|
|
10622
|
+
const newKey = normalizeEnvKey(newUrl);
|
|
10623
|
+
const envName = getEnvironmentName(newApiConfig);
|
|
10624
|
+
if (prevKey === newKey) return {
|
|
10625
|
+
url: newUrl,
|
|
10626
|
+
envName,
|
|
10627
|
+
changed: false,
|
|
10628
|
+
authenticated: hasValidAuth(config.auth)
|
|
10629
|
+
};
|
|
10630
|
+
const authByEnv = { ...config.authByEnv || {} };
|
|
10631
|
+
if (config.auth) authByEnv[prevKey] = config.auth;
|
|
10632
|
+
else delete authByEnv[prevKey];
|
|
10633
|
+
const restored = authByEnv[newKey];
|
|
10634
|
+
config.apiConfig = newApiConfig;
|
|
10635
|
+
config.authByEnv = authByEnv;
|
|
10636
|
+
config.auth = restored;
|
|
10637
|
+
await this.save();
|
|
10638
|
+
return {
|
|
10639
|
+
url: newUrl,
|
|
10640
|
+
envName,
|
|
10641
|
+
changed: true,
|
|
10642
|
+
authenticated: hasValidAuth(restored)
|
|
10643
|
+
};
|
|
10644
|
+
}
|
|
10645
|
+
/**
|
|
10547
10646
|
* Get project config directory (if any)
|
|
10548
10647
|
*/
|
|
10549
10648
|
getProjectConfigDir() {
|
|
@@ -10658,4 +10757,4 @@ var ConfigStore = class {
|
|
|
10658
10757
|
}
|
|
10659
10758
|
};
|
|
10660
10759
|
//#endregion
|
|
10661
|
-
export {
|
|
10760
|
+
export { ProjectEvents as $, GenerateImageToolCallSchema as A, dayjsConfig_default as At, InviteEvents as B, sanitizeTelemetryError as Bt, ElabsEvents as C, UnauthorizedError as Ct, ForbiddenError as D, VideoModels as Dt, FileEvents as E, VideoGenerationUsageTransaction as Et, ImageEditUsageTransaction as F, isGPTImage2Model as Ft, ModalEvents as G, buildRateLimitLogEntry as Gt, KnowledgeType as H, settingsMap as Ht, ImageGenerationUsageTransaction as I, isGPTImageModel as It, OpenAIEmbeddingModel as J, parseRateLimitHeaders as Jt, ModelBackend as K, extractSnippetMeta as Kt, ImageModels as L, isZodError as Lt, GenericCreditDeductTransaction as M, getDataLakeTags as Mt, HTTPError as N, getMcpProviderMetadata as Nt, FriendshipEvents as O, XAI_IMAGE_MODELS as Ot, HttpStatus as P, getViewById as Pt, ProfileEvents as Q, InboxEvents as R, obfuscateApiKey as Rt, DashboardParamsSchema as S, UiNavigationEvents as St, FeedbackEvents as T, VIDEO_SIZE_CONSTRAINTS as Tt, LLMEvents as U, validateJupyterKernelName as Ut, InviteType as V, secureParameters as Vt, MiscEvents as W, validateNotebookPath as Wt, Permission as X, OpenAIImageGenerationInput as Y, CollectionType as Yt, PermissionDeniedError as Z, ChatCompletionCreateInputSchema as _, TaskScheduleHandler as _t, ALERT_THRESHOLDS as a, ReceivedCreditTransaction as at, CompletionApiUsageTransaction as b, ToolUsageTransaction as bt, ApiKeyScope as c, ResearchModeParamsSchema as ct, ArtifactTypeSchema as d, ResearchTaskType as dt, PromptIntentSchema as et, AuthEvents as f, SessionEvents as ft, CREDIT_DEDUCT_TRANSACTION_TYPES as g, TagType as gt, BadRequestError as h, SupportedFabFileMimeTypes as ht, getEnvironmentName as i, RealtimeVoiceUsageTransaction as it, GenericCreditAddTransaction as j, getAccessibleDataLakes as jt, GEMINI_IMAGE_MODELS as k, b4mLLMTools as kt, ApiKeyType as l, ResearchTaskExecutionType as lt, BFL_SAFETY_TOLERANCE as m, SubscriptionCreditTransaction as mt, logger as n, PurchaseTransaction as nt, AiEvents as o, RechartsChartTypeList as ot, BFL_IMAGE_MODELS as p, SpeechToTextUsageTransaction as pt, NotFoundError as q, isNearLimit as qt, getApiUrl as r, QuestMasterParamsSchema as rt, ApiKeyEvents as s, RegInviteEvents as st, ConfigStore as t, PromptMetaZodSchema as tt, AppFileEvents as u, ResearchTaskPeriodicFrequencyType as ut, ChatModels as v, TextGenerationUsageTransaction as vt, FavoriteDocumentType as w, UnprocessableEntityError as wt, CorruptedFileError as x, TransferCreditTransaction as xt, ClaudeArtifactMimeTypes as y, TooManyRequestsError as yt, InternalServerError as z, resolveNavigationIntents as zt };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { t as version } from "../package-
|
|
2
|
+
import { t as version } from "../package-CMkVxGGC.mjs";
|
|
3
3
|
import { n as compareSemver, r as fetchLatestVersion } from "../updateChecker-D67NPlS5.mjs";
|
|
4
4
|
import { t as checkRipgrep } from "../ripgrepCheck-BmkyTK2i.mjs";
|
|
5
5
|
import { execSync } from "child_process";
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { t as ConfigStore } from "../ConfigStore-C3tokQej.mjs";
|
|
3
|
+
//#region src/commands/envCommand.ts
|
|
4
|
+
/**
|
|
5
|
+
* Environment switching for the `--dev` / `--prod` launch flags.
|
|
6
|
+
*
|
|
7
|
+
* Flipping the target persists the choice to ~/.bike4mind/config.json, so a
|
|
8
|
+
* bare `b4m` always reuses whichever environment you last selected. Auth tokens
|
|
9
|
+
* are cached per-environment, so flipping back and forth doesn't force a
|
|
10
|
+
* re-login (see ConfigStore.switchApiEnvironment).
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* Apply a `--dev` / `--prod` launch flag: switch the persisted API environment
|
|
14
|
+
* and print a concise banner describing the result. Runs before the app boots.
|
|
15
|
+
*/
|
|
16
|
+
async function applyEnvironmentFlag(target) {
|
|
17
|
+
const result = await new ConfigStore().switchApiEnvironment(target);
|
|
18
|
+
if (result.changed) console.log(`🔀 Switched API environment → ${result.envName} (${result.url})`);
|
|
19
|
+
else console.log(`🌍 Already on ${result.envName} (${result.url})`);
|
|
20
|
+
if (result.authenticated) console.log(" ✅ Reusing your saved login for this environment.");
|
|
21
|
+
else console.log(" 🔓 Not logged in here yet — run /login once the CLI starts.");
|
|
22
|
+
console.log("");
|
|
23
|
+
}
|
|
24
|
+
//#endregion
|
|
25
|
+
export { applyEnvironmentFlag };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
import { n as logger, t as ConfigStore } from "../ConfigStore-
|
|
2
|
+
import { C as WebSocketToolExecutor, D as ServerLlmBackend, E as WebSocketLlmBackend, F as generateCliTools, G as buildSystemPrompt, J as ReActAgent, N as loadContextFiles, P as PermissionManager, Q as SessionStore, S as ApiClient, T as FallbackLlmBackend, U as setWebSocketToolExecutor, X as CheckpointStore, Y as CustomCommandStore, _ as createAgentDelegateTool, b as createSkillTool, d as createFindDefinitionTool, f as createTodoStore, g as BackgroundAgentManager, h as createBackgroundAgentTools, k as McpManager, m as createCoordinateTaskTool, p as createWriteTodosTool, q as isReadOnlyTool, u as createGetFileStructureTool, v as AgentStore, w as WebSocketConnectionManager, y as SubagentOrchestrator } from "../tools-QQ6ibgPF.mjs";
|
|
3
|
+
import { n as logger, r as getApiUrl, t as ConfigStore } from "../ConfigStore-C3tokQej.mjs";
|
|
4
4
|
import { t as DEFAULT_SANDBOX_CONFIG } from "../types-LyRNHOiS.mjs";
|
|
5
5
|
import { t as createSandboxRuntime } from "../SandboxRuntimeAdapter-ChGlxSGQ.mjs";
|
|
6
6
|
import { t as SandboxOrchestrator } from "../SandboxOrchestrator-BoINxbX4.mjs";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { t as version } from "../package-
|
|
2
|
+
import { t as version } from "../package-CMkVxGGC.mjs";
|
|
3
3
|
import { i as forceCheckForUpdate } from "../updateChecker-D67NPlS5.mjs";
|
|
4
4
|
import { t as checkRipgrep } from "../ripgrepCheck-BmkyTK2i.mjs";
|
|
5
5
|
import { execSync } from "child_process";
|
package/dist/index.mjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { $ as
|
|
2
|
+
import { $ as OAuthClient, A as substituteArguments, B as DEFAULT_THOROUGHNESS, C as WebSocketToolExecutor, D as ServerLlmBackend, E as WebSocketLlmBackend, F as generateCliTools, G as buildSystemPrompt, H as registerFeatureModuleTools, I as ALWAYS_DENIED_FOR_AGENTS, J as ReActAgent, K as buildSkillsPromptSection, L as DEFAULT_AGENT_MODEL, M as extractCompactInstructions, N as loadContextFiles, O as isTransientNetworkError, P as PermissionManager, Q as SessionStore, R as DEFAULT_MAX_ITERATIONS, S as ApiClient, T as FallbackLlmBackend, U as setWebSocketToolExecutor, V as clearFeatureModuleTools, W as getPlanModeFilePath, X as CheckpointStore, Y as CustomCommandStore, Z as CommandHistoryStore, _ as createAgentDelegateTool, a as createBlockerTools, at as searchFiles, b as createSkillTool, c as createDecisionStore, d as createFindDefinitionTool, et as hasFileReferences, f as createTodoStore, g as BackgroundAgentManager, h as createBackgroundAgentTools, i as createBlockerStore, it as formatFileSize, j as formatStep, k as McpManager, l as formatDecisionsOutput, m as createCoordinateTaskTool, n as createReviewGateTool, nt as searchCommands, o as formatBlockersOutput, ot as warmFileCache, p as createWriteTodosTool, q as isReadOnlyTool, r as formatReviewGatesOutput, rt as mergeCommands, s as createDecisionLogTool, t as createReviewGateStore, tt as processFileReferences, u as createGetFileStructureTool, v as AgentStore, w as WebSocketConnectionManager, x as parseAgentConfig, y as SubagentOrchestrator, z as DEFAULT_RETRY_CONFIG } from "./tools-QQ6ibgPF.mjs";
|
|
3
3
|
import { n as useCliStore, t as selectActiveBackgroundAgents } from "./store-DV5s-qni.mjs";
|
|
4
|
-
import {
|
|
5
|
-
import { t as version } from "./package-
|
|
4
|
+
import { Ut as validateJupyterKernelName, Wt as validateNotebookPath$1, g as CREDIT_DEDUCT_TRANSACTION_TYPES, i as getEnvironmentName, n as logger, r as getApiUrl, t as ConfigStore, v as ChatModels } from "./ConfigStore-C3tokQej.mjs";
|
|
5
|
+
import { t as version } from "./package-CMkVxGGC.mjs";
|
|
6
6
|
import { t as checkForUpdate } from "./updateChecker-D67NPlS5.mjs";
|
|
7
7
|
import React, { useCallback, useEffect, useMemo, useReducer, useRef, useState } from "react";
|
|
8
8
|
import { Box, Static, Text, render, useApp, useInput, usePaste, useStdout } from "ink";
|
|
@@ -5441,6 +5441,7 @@ var BridgePresence = class {
|
|
|
5441
5441
|
*/
|
|
5442
5442
|
async start(opts) {
|
|
5443
5443
|
if (this.started) return this.instanceId !== null;
|
|
5444
|
+
this.stopped = false;
|
|
5444
5445
|
this.started = true;
|
|
5445
5446
|
const config = await readBridgeConfig();
|
|
5446
5447
|
if (!config) {
|
|
@@ -5504,9 +5505,19 @@ var BridgePresence = class {
|
|
|
5504
5505
|
this.emitQueue = this.emitQueue.then(task, task);
|
|
5505
5506
|
return this.emitQueue;
|
|
5506
5507
|
}
|
|
5507
|
-
/**
|
|
5508
|
+
/**
|
|
5509
|
+
* Tear down the tavern presence cleanly. Halts the announce-retry and
|
|
5510
|
+
* command-WS reconnect loops, closes the socket, and best-effort signals
|
|
5511
|
+
* disconnect to the bridge.
|
|
5512
|
+
*
|
|
5513
|
+
* After this resolves the instance is fully reset, so a later `start()`
|
|
5514
|
+
* re-announces — the same singleton can be toggled off (Tavern feature
|
|
5515
|
+
* disabled at runtime) and back on without restarting the CLI. The
|
|
5516
|
+
* `stopped` latch is left true here purely so any straggler retry callback
|
|
5517
|
+
* already queued short-circuits; `start()` clears it.
|
|
5518
|
+
*/
|
|
5508
5519
|
async stop(reason = "cli_exit") {
|
|
5509
|
-
if (this.stopped) return;
|
|
5520
|
+
if (this.stopped || !this.started) return;
|
|
5510
5521
|
this.stopped = true;
|
|
5511
5522
|
if (this.reconnectTimer) {
|
|
5512
5523
|
clearTimeout(this.reconnectTimer);
|
|
@@ -5526,6 +5537,16 @@ var BridgePresence = class {
|
|
|
5526
5537
|
instanceId: this.instanceId,
|
|
5527
5538
|
reason
|
|
5528
5539
|
}).catch(() => {});
|
|
5540
|
+
this.started = false;
|
|
5541
|
+
this.instanceId = null;
|
|
5542
|
+
this.config = null;
|
|
5543
|
+
this.startOpts = null;
|
|
5544
|
+
this.pendingWorkspaceName = null;
|
|
5545
|
+
this.pendingCapabilities = null;
|
|
5546
|
+
this.pendingSource = null;
|
|
5547
|
+
this.announceAttempts = 0;
|
|
5548
|
+
this.reconnectAttempts = 0;
|
|
5549
|
+
this.emitQueue = Promise.resolve();
|
|
5529
5550
|
}
|
|
5530
5551
|
async announce(body) {
|
|
5531
5552
|
try {
|
|
@@ -5864,7 +5885,7 @@ function CliApp() {
|
|
|
5864
5885
|
startupLog.push(`✅ Authenticated (expires in ${daysUntilExpiry} day${daysUntilExpiry !== 1 ? "s" : ""})`);
|
|
5865
5886
|
const apiBaseURL = getApiUrl(config.apiConfig);
|
|
5866
5887
|
const envName = getEnvironmentName(config.apiConfig);
|
|
5867
|
-
|
|
5888
|
+
startupLog.unshift(`🌍 API Environment: ${envName} (${apiBaseURL})`);
|
|
5868
5889
|
const apiClient = new ApiClient(apiBaseURL, state.configStore);
|
|
5869
5890
|
const tokenGetter = async () => {
|
|
5870
5891
|
return (await state.configStore.getAuthTokens())?.accessToken ?? null;
|
|
@@ -6499,8 +6520,13 @@ function CliApp() {
|
|
|
6499
6520
|
status: "running"
|
|
6500
6521
|
});
|
|
6501
6522
|
};
|
|
6523
|
+
const tavernPresenceEnabled = state.config?.features?.tavern ?? false;
|
|
6502
6524
|
useEffect(() => {
|
|
6503
6525
|
if (!isInitialized) return;
|
|
6526
|
+
if (!tavernPresenceEnabled) {
|
|
6527
|
+
bridgePresence.stop("tavern_disabled");
|
|
6528
|
+
return;
|
|
6529
|
+
}
|
|
6504
6530
|
let cancelled = false;
|
|
6505
6531
|
bridgePresence.setCallbacks({
|
|
6506
6532
|
onSendPrompt: (text) => handleMessageRef.current?.(text),
|
|
@@ -6524,7 +6550,7 @@ function CliApp() {
|
|
|
6524
6550
|
return () => {
|
|
6525
6551
|
cancelled = true;
|
|
6526
6552
|
};
|
|
6527
|
-
}, [isInitialized]);
|
|
6553
|
+
}, [isInitialized, tavernPresenceEnabled]);
|
|
6528
6554
|
/**
|
|
6529
6555
|
* Handle custom command execution with proper display
|
|
6530
6556
|
* Shows concise user message but sends full template to agent
|
|
@@ -6928,8 +6954,13 @@ function CliApp() {
|
|
|
6928
6954
|
return;
|
|
6929
6955
|
}
|
|
6930
6956
|
}
|
|
6931
|
-
const
|
|
6932
|
-
|
|
6957
|
+
const rawMessage = error instanceof Error ? error.message : String(error);
|
|
6958
|
+
if (error instanceof Error && isTransientNetworkError(error)) {
|
|
6959
|
+
console.error("\n❌ The connection to the server dropped mid-response. Type \"continue\" to resume.\n");
|
|
6960
|
+
logger.debug(`Full error details: ${error.stack || error.message}`);
|
|
6961
|
+
return;
|
|
6962
|
+
}
|
|
6963
|
+
console.error(`\n❌ ${rawMessage}\n`);
|
|
6933
6964
|
logger.debug(`Full error details: ${error instanceof Error ? error.stack || error.message : String(error)}`);
|
|
6934
6965
|
} finally {
|
|
6935
6966
|
const wasAborted = abortController.signal.aborted;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { $ as
|
|
2
|
+
import { $ as ProjectEvents, A as GenerateImageToolCallSchema, At as dayjsConfig_default, B as InviteEvents, Bt as sanitizeTelemetryError, C as ElabsEvents, Ct as UnauthorizedError, D as ForbiddenError, Dt as VideoModels, E as FileEvents, Et as VideoGenerationUsageTransaction, F as ImageEditUsageTransaction, Ft as isGPTImage2Model, G as ModalEvents, Gt as buildRateLimitLogEntry, H as KnowledgeType, Ht as settingsMap, I as ImageGenerationUsageTransaction, It as isGPTImageModel, J as OpenAIEmbeddingModel, Jt as parseRateLimitHeaders, K as ModelBackend, Kt as extractSnippetMeta, L as ImageModels, Lt as isZodError, M as GenericCreditDeductTransaction, Mt as getDataLakeTags, N as HTTPError, Nt as getMcpProviderMetadata, O as FriendshipEvents, Ot as XAI_IMAGE_MODELS, P as HttpStatus, Pt as getViewById, Q as ProfileEvents, R as InboxEvents, Rt as obfuscateApiKey, S as DashboardParamsSchema, St as UiNavigationEvents, T as FeedbackEvents, Tt as VIDEO_SIZE_CONSTRAINTS, U as LLMEvents, V as InviteType, Vt as secureParameters, W as MiscEvents, X as Permission, Y as OpenAIImageGenerationInput, Yt as CollectionType, Z as PermissionDeniedError, _ as ChatCompletionCreateInputSchema, _t as TaskScheduleHandler, a as ALERT_THRESHOLDS, at as ReceivedCreditTransaction, b as CompletionApiUsageTransaction, bt as ToolUsageTransaction, c as ApiKeyScope, ct as ResearchModeParamsSchema, d as ArtifactTypeSchema, dt as ResearchTaskType, et as PromptIntentSchema, f as AuthEvents, ft as SessionEvents, gt as TagType, h as BadRequestError, ht as SupportedFabFileMimeTypes, it as RealtimeVoiceUsageTransaction, j as GenericCreditAddTransaction, jt as getAccessibleDataLakes, k as GEMINI_IMAGE_MODELS, kt as b4mLLMTools, l as ApiKeyType, lt as ResearchTaskExecutionType, m as BFL_SAFETY_TOLERANCE, mt as SubscriptionCreditTransaction, n as logger, nt as PurchaseTransaction, o as AiEvents, ot as RechartsChartTypeList, p as BFL_IMAGE_MODELS, pt as SpeechToTextUsageTransaction, q as NotFoundError, qt as isNearLimit, rt as QuestMasterParamsSchema, s as ApiKeyEvents, st as RegInviteEvents, t as ConfigStore, tt as PromptMetaZodSchema, u as AppFileEvents, ut as ResearchTaskPeriodicFrequencyType, v as ChatModels, vt as TextGenerationUsageTransaction, w as FavoriteDocumentType, wt as UnprocessableEntityError, x as CorruptedFileError, xt as TransferCreditTransaction, y as ClaudeArtifactMimeTypes, yt as TooManyRequestsError, z as InternalServerError, zt as resolveNavigationIntents } from "./ConfigStore-C3tokQej.mjs";
|
|
3
3
|
import { a as isUserLockedOut, c as userCanDisableMFA, d as userRequiresMFA, f as verifyBackupCode, i as getLockoutTimeRemaining, l as userEligibleForMFA, n as generateBackupCodes, o as recordFailedAttempt, p as verifyTOTPToken, r as generateTOTPSetup, s as shouldResetFailedAttempts, t as clearFailedAttempts, u as userHasMFAConfigured } from "./utils-PpNti-tY.mjs";
|
|
4
4
|
import { n as isPathAllowed, t as assertPathAllowed } from "./pathValidation-D8tjkQXE-1HwvsuYT.mjs";
|
|
5
|
-
import { t as version } from "./package-
|
|
5
|
+
import { t as version } from "./package-CMkVxGGC.mjs";
|
|
6
6
|
import { execFile, execFileSync, spawn } from "child_process";
|
|
7
7
|
import crypto, { createHash, randomBytes } from "crypto";
|
|
8
8
|
import { existsSync, promises, readFileSync, readdirSync, rmSync, statSync, unlinkSync, writeFileSync } from "fs";
|
|
@@ -22290,7 +22290,7 @@ var CliLogger = class extends Logger {
|
|
|
22290
22290
|
* 5. Route to server or execute locally via ToolRouter
|
|
22291
22291
|
* 6. Record observation in agent's step history
|
|
22292
22292
|
*/
|
|
22293
|
-
function wrapToolWithPermission(tool, permissionManager, showPermissionPrompt, agentContext, configStore, apiClient, sandboxOrchestrator) {
|
|
22293
|
+
function wrapToolWithPermission(tool, permissionManager, showPermissionPrompt, agentContext, configStore, apiClient, sandboxOrchestrator, allowedDirectories) {
|
|
22294
22294
|
const originalFn = tool.toolFn;
|
|
22295
22295
|
const toolName = tool.toolSchema.name;
|
|
22296
22296
|
return {
|
|
@@ -22329,10 +22329,18 @@ function wrapToolWithPermission(tool, permissionManager, showPermissionPrompt, a
|
|
|
22329
22329
|
* offer retry on sandbox failure, and record observation.
|
|
22330
22330
|
*/
|
|
22331
22331
|
async function executeAndRecord() {
|
|
22332
|
-
let result
|
|
22332
|
+
let result;
|
|
22333
|
+
try {
|
|
22334
|
+
result = await executeTool(toolName, effectiveArgs, apiClient, originalFn);
|
|
22335
|
+
} catch (err) {
|
|
22336
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
22337
|
+
if (!isPathAccessDenial(msg)) throw err;
|
|
22338
|
+
result = msg;
|
|
22339
|
+
}
|
|
22333
22340
|
cleanupSandboxFiles(effectiveArgs?._sandboxCleanup);
|
|
22334
22341
|
await captureViolations(isSandboxed, result, args?.command, sandboxOrchestrator);
|
|
22335
22342
|
result = await retrySandboxFailure(isSandboxed, result, toolName, args, apiClient, originalFn, showPermissionPrompt);
|
|
22343
|
+
result = await retryPathAccessDenial(result, toolName, effectiveArgs, allowedDirectories, configStore, apiClient, originalFn, showPermissionPrompt);
|
|
22336
22344
|
agentContext.observationQueue.push({
|
|
22337
22345
|
toolName,
|
|
22338
22346
|
result
|
|
@@ -22377,6 +22385,64 @@ async function retrySandboxFailure(isSandboxed, result, toolName, originalArgs,
|
|
|
22377
22385
|
return result;
|
|
22378
22386
|
}
|
|
22379
22387
|
/**
|
|
22388
|
+
* Matches the three shapes of filesystem allow-list denial emitted by core:
|
|
22389
|
+
* - file tools: `Access denied: Cannot <op> files outside allowed directories.`
|
|
22390
|
+
* - glob_files: `Access denied: Cannot search outside allowed directories.` (no "files")
|
|
22391
|
+
* - grep_search: `Path validation failed: "<p>" resolves outside allowed directories.`
|
|
22392
|
+
* The `files` token is optional so glob_files' wording is covered too.
|
|
22393
|
+
*/
|
|
22394
|
+
const PATH_ACCESS_DENIAL_RE = /Access denied: Cannot \w+ (?:files )?outside allowed directories|Path validation failed: .* resolves outside allowed directories/;
|
|
22395
|
+
function isPathAccessDenial(text) {
|
|
22396
|
+
return PATH_ACCESS_DENIAL_RE.test(text);
|
|
22397
|
+
}
|
|
22398
|
+
/**
|
|
22399
|
+
* Derive the directory to grant so the blocked operation can succeed on retry.
|
|
22400
|
+
* File tools (`file_read`, `create_file`, `edit_local_file`, `delete_file`)
|
|
22401
|
+
* target a file → grant its containing directory. `grep_search` / `glob_files`
|
|
22402
|
+
* target a directory → grant it directly. Returns an absolute path, or null if
|
|
22403
|
+
* no path argument is present.
|
|
22404
|
+
*/
|
|
22405
|
+
function deriveGrantDirectory(toolName, args) {
|
|
22406
|
+
const raw = args?.path ?? args?.dir_path;
|
|
22407
|
+
if (typeof raw !== "string" || raw.length === 0) return null;
|
|
22408
|
+
const resolved = path.isAbsolute(raw) ? raw : path.resolve(process.cwd(), raw);
|
|
22409
|
+
return new Set(["grep_search", "glob_files"]).has(toolName) ? resolved : path.dirname(resolved);
|
|
22410
|
+
}
|
|
22411
|
+
/**
|
|
22412
|
+
* If a tool result indicates the operation was blocked by the filesystem
|
|
22413
|
+
* allow-list, prompt the user to grant access to the relevant directory.
|
|
22414
|
+
* On approval the directory is pushed onto the live allow-list (so the very
|
|
22415
|
+
* next core tool call sees it — same array reference held by the tool
|
|
22416
|
+
* context), persisted to config on "Always allow", and the tool is retried.
|
|
22417
|
+
*
|
|
22418
|
+
* This is the runtime, on-demand equivalent of `/add-dir`: instead of failing
|
|
22419
|
+
* hard when the agent reaches outside the workspace, it asks — like Claude
|
|
22420
|
+
* Code does — and continues once the user says yes.
|
|
22421
|
+
*/
|
|
22422
|
+
async function retryPathAccessDenial(result, toolName, args, allowedDirectories, configStore, apiClient, originalFn, showPermissionPrompt) {
|
|
22423
|
+
if (!allowedDirectories || !isPathAccessDenial(result)) return result;
|
|
22424
|
+
const grantDir = deriveGrantDirectory(toolName, args);
|
|
22425
|
+
if (!grantDir) return result;
|
|
22426
|
+
if (allowedDirectories.includes(grantDir)) return result;
|
|
22427
|
+
const response = await showPermissionPrompt(toolName, args, `🔒 DIRECTORY ACCESS — "${toolName}" needs a path outside the current workspace.\n\n- Grant access to this directory:\n ${grantDir}\n- "Allow for this session" grants access until the CLI exits.\n- "Always allow" also saves it to your config so it persists across sessions.`);
|
|
22428
|
+
if (response.action === "deny") return result;
|
|
22429
|
+
const oneShot = response.action === "allow-once";
|
|
22430
|
+
allowedDirectories.push(grantDir);
|
|
22431
|
+
if (response.action === "allow-always") try {
|
|
22432
|
+
await configStore.addDirectory(grantDir);
|
|
22433
|
+
} catch {}
|
|
22434
|
+
try {
|
|
22435
|
+
return await executeTool(toolName, args, apiClient, originalFn);
|
|
22436
|
+
} catch (err) {
|
|
22437
|
+
return err instanceof Error ? err.message : String(err);
|
|
22438
|
+
} finally {
|
|
22439
|
+
if (oneShot) {
|
|
22440
|
+
const idx = allowedDirectories.lastIndexOf(grantDir);
|
|
22441
|
+
if (idx !== -1) allowedDirectories.splice(idx, 1);
|
|
22442
|
+
}
|
|
22443
|
+
}
|
|
22444
|
+
}
|
|
22445
|
+
/**
|
|
22380
22446
|
* Clean up temporary sandbox files (e.g., Seatbelt profiles).
|
|
22381
22447
|
* Fails silently — cleanup is best-effort.
|
|
22382
22448
|
*/
|
|
@@ -22605,12 +22671,14 @@ async function generateCliTools(userId, llm, model, permissionManager, showPermi
|
|
|
22605
22671
|
"web_fetch"
|
|
22606
22672
|
].filter((name) => name in b4mTools).map((name) => [name, b4mTools[name]]));
|
|
22607
22673
|
const cliOnlyTools = await getCliOnlyTools();
|
|
22608
|
-
const
|
|
22674
|
+
const tools_to_generate = {
|
|
22609
22675
|
...filteredB4mTools,
|
|
22610
22676
|
...cliOnlyTools
|
|
22611
|
-
}
|
|
22677
|
+
};
|
|
22678
|
+
const liveAllowedDirectories = allowedDirectories ?? [];
|
|
22679
|
+
const toolsMap = generateTools(userId, user, logger, dbAdapters, storage, storage, statusUpdate, onStart, onFinish, llm, {}, model, void 0, tools_to_generate, liveAllowedDirectories);
|
|
22612
22680
|
let tools = Object.entries(toolsMap).map(([_, tool]) => {
|
|
22613
|
-
return wrapToolWithCheckpointing(wrapToolWithPermission(tool, permissionManager, showPermissionPrompt, agentContext, configStore, apiClient, sandboxOrchestrator), checkpointStore ?? null);
|
|
22681
|
+
return wrapToolWithCheckpointing(wrapToolWithPermission(tool, permissionManager, showPermissionPrompt, agentContext, configStore, apiClient, sandboxOrchestrator, liveAllowedDirectories), checkpointStore ?? null);
|
|
22614
22682
|
});
|
|
22615
22683
|
if (toolFilter) {
|
|
22616
22684
|
const { allowedTools, deniedTools } = toolFilter;
|
|
@@ -22779,24 +22847,6 @@ var PermissionManager = class {
|
|
|
22779
22847
|
};
|
|
22780
22848
|
}
|
|
22781
22849
|
};
|
|
22782
|
-
//#endregion
|
|
22783
|
-
//#region src/utils/apiUrl.ts
|
|
22784
|
-
const BIKE4MIND_URL = "https://app.bike4mind.com";
|
|
22785
|
-
/**
|
|
22786
|
-
* Resolve API URL based on configuration
|
|
22787
|
-
* Returns custom URL if set, otherwise Bike4Mind main service
|
|
22788
|
-
*/
|
|
22789
|
-
function getApiUrl(configApiConfig) {
|
|
22790
|
-
if (configApiConfig?.customUrl) return configApiConfig.customUrl;
|
|
22791
|
-
return BIKE4MIND_URL;
|
|
22792
|
-
}
|
|
22793
|
-
/**
|
|
22794
|
-
* Get human-readable API type name
|
|
22795
|
-
*/
|
|
22796
|
-
function getEnvironmentName(configApiConfig) {
|
|
22797
|
-
if (configApiConfig?.customUrl) return "Self-Hosted";
|
|
22798
|
-
return "Bike4Mind";
|
|
22799
|
-
}
|
|
22800
22850
|
const PROJECT_CONTEXT_FILES = [
|
|
22801
22851
|
"CLAUDE.local.md",
|
|
22802
22852
|
"CLAUDE.md",
|
|
@@ -23630,6 +23680,35 @@ var StreamAccumulator = class {
|
|
|
23630
23680
|
//#endregion
|
|
23631
23681
|
//#region src/llm/ServerLlmBackend.ts
|
|
23632
23682
|
/**
|
|
23683
|
+
* Connection-level failures that should be retried rather than surfaced to the
|
|
23684
|
+
* user. Mirrors the canonical retryable-error list in `@bike4mind/llm-adapters`
|
|
23685
|
+
* (retry.ts, issues #6936 / #6892): the most common offender is a TLS socket
|
|
23686
|
+
* close mid-stream, which Node surfaces as `Error: aborted` thrown from
|
|
23687
|
+
* `node:_http_client` `socketCloseListener`. This happens when the SSE
|
|
23688
|
+
* connection sits idle during a long extended-thinking step and an intermediary
|
|
23689
|
+
* (or the socket itself) times out the idle connection.
|
|
23690
|
+
*
|
|
23691
|
+
* Crucially this is NOT a user cancel — those are detected separately via
|
|
23692
|
+
* `options.abortSignal` before this classifier is consulted. Matching is on the
|
|
23693
|
+
* lowercased message so we catch the various wordings undici/Node emit.
|
|
23694
|
+
*/
|
|
23695
|
+
const TRANSIENT_NETWORK_ERROR_PATTERNS = [
|
|
23696
|
+
"aborted",
|
|
23697
|
+
"socket closed",
|
|
23698
|
+
"socket hang up",
|
|
23699
|
+
"connection closed",
|
|
23700
|
+
"econnreset",
|
|
23701
|
+
"etimedout",
|
|
23702
|
+
"terminated",
|
|
23703
|
+
"network error",
|
|
23704
|
+
"fetch failed",
|
|
23705
|
+
"und_err_socket"
|
|
23706
|
+
];
|
|
23707
|
+
function isTransientNetworkError(error) {
|
|
23708
|
+
const message = error.message?.toLowerCase() ?? "";
|
|
23709
|
+
return TRANSIENT_NETWORK_ERROR_PATTERNS.some((pattern) => message.includes(pattern));
|
|
23710
|
+
}
|
|
23711
|
+
/**
|
|
23633
23712
|
* Server-side LLM backend that proxies requests through Bike4Mind API
|
|
23634
23713
|
* Uses Server-Sent Events (SSE) for streaming responses
|
|
23635
23714
|
* API keys remain secure on server - never exposed to CLI
|
|
@@ -23654,19 +23733,35 @@ var ServerLlmBackend = class ServerLlmBackend {
|
|
|
23654
23733
|
*/
|
|
23655
23734
|
async complete(model, messages, options, callback) {
|
|
23656
23735
|
let lastError;
|
|
23736
|
+
let delivered = false;
|
|
23737
|
+
const trackingCallback = (text, info) => {
|
|
23738
|
+
delivered = true;
|
|
23739
|
+
return callback(text, info);
|
|
23740
|
+
};
|
|
23657
23741
|
for (let attempt = 0; attempt <= ServerLlmBackend.MAX_STREAM_RETRIES; attempt++) {
|
|
23658
23742
|
if (attempt > 0) logger.warn(`[ServerLlmBackend] Retrying stream (attempt ${attempt + 1}/${ServerLlmBackend.MAX_STREAM_RETRIES + 1})...`);
|
|
23659
23743
|
try {
|
|
23660
|
-
await this.completeOnce(model, messages, options,
|
|
23744
|
+
await this.completeOnce(model, messages, options, trackingCallback);
|
|
23661
23745
|
return;
|
|
23662
23746
|
} catch (error) {
|
|
23663
23747
|
lastError = error instanceof Error ? error : new Error(String(error));
|
|
23664
|
-
|
|
23665
|
-
|
|
23666
|
-
|
|
23667
|
-
|
|
23748
|
+
if (options.abortSignal?.aborted) throw lastError;
|
|
23749
|
+
if (delivered) {
|
|
23750
|
+
if (isTransientNetworkError(lastError)) {
|
|
23751
|
+
logger.warn(`[ServerLlmBackend] Ignoring post-delivery transient stream error: ${lastError.message}`);
|
|
23752
|
+
return;
|
|
23753
|
+
}
|
|
23754
|
+
throw lastError;
|
|
23755
|
+
}
|
|
23756
|
+
if (!(lastError.message.includes("Stream ended prematurely") || isTransientNetworkError(lastError))) throw lastError;
|
|
23757
|
+
logger.warn(`[ServerLlmBackend] Transient stream failure (attempt ${attempt + 1}/${ServerLlmBackend.MAX_STREAM_RETRIES + 1}): ${lastError.message}`);
|
|
23758
|
+
if (attempt < ServerLlmBackend.MAX_STREAM_RETRIES) await new Promise((resolve) => setTimeout(resolve, 500 * (attempt + 1)));
|
|
23668
23759
|
}
|
|
23669
23760
|
}
|
|
23761
|
+
if (lastError && isTransientNetworkError(lastError) && !options.abortSignal?.aborted) {
|
|
23762
|
+
logger.error("[ServerLlmBackend] Stream failed after all retries due to a network drop", lastError);
|
|
23763
|
+
throw new Error("The connection to the Bike4Mind server dropped mid-response (likely a network timeout during a long thinking step). It was retried automatically but kept failing — type \"continue\" to resume.");
|
|
23764
|
+
}
|
|
23670
23765
|
throw lastError ?? /* @__PURE__ */ new Error("Stream failed after all retry attempts");
|
|
23671
23766
|
}
|
|
23672
23767
|
/**
|
|
@@ -27524,4 +27619,4 @@ function createReviewGateStore(onUpdate) {
|
|
|
27524
27619
|
};
|
|
27525
27620
|
}
|
|
27526
27621
|
//#endregion
|
|
27527
|
-
export {
|
|
27622
|
+
export { OAuthClient as $, substituteArguments as A, DEFAULT_THOROUGHNESS as B, WebSocketToolExecutor as C, ServerLlmBackend as D, WebSocketLlmBackend as E, generateCliTools as F, buildSystemPrompt as G, registerFeatureModuleTools as H, ALWAYS_DENIED_FOR_AGENTS as I, ReActAgent as J, buildSkillsPromptSection as K, DEFAULT_AGENT_MODEL as L, extractCompactInstructions as M, loadContextFiles as N, isTransientNetworkError as O, PermissionManager as P, SessionStore as Q, DEFAULT_MAX_ITERATIONS as R, ApiClient as S, FallbackLlmBackend as T, setWebSocketToolExecutor as U, clearFeatureModuleTools as V, getPlanModeFilePath as W, CheckpointStore as X, CustomCommandStore as Y, CommandHistoryStore as Z, createAgentDelegateTool as _, createBlockerTools as a, searchFiles as at, createSkillTool as b, createDecisionStore as c, createFindDefinitionTool as d, hasFileReferences as et, createTodoStore as f, BackgroundAgentManager as g, createBackgroundAgentTools as h, createBlockerStore as i, formatFileSize$1 as it, formatStep as j, McpManager as k, formatDecisionsOutput as l, createCoordinateTaskTool as m, createReviewGateTool as n, searchCommands as nt, formatBlockersOutput as o, warmFileCache as ot, createWriteTodosTool as p, isReadOnlyTool as q, formatReviewGatesOutput as r, mergeCommands as rt, createDecisionLogTool as s, createReviewGateStore as t, processFileReferences as tt, createGetFileStructureTool as u, AgentStore as v, WebSocketConnectionManager as w, parseAgentConfig as x, SubagentOrchestrator as y, DEFAULT_RETRY_CONFIG as z };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bike4mind/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Interactive CLI tool for Bike4Mind with ReAct agents",
|
|
6
6
|
"license": "UNLICENSED",
|
|
@@ -107,8 +107,8 @@
|
|
|
107
107
|
"zod": "^4.4.3",
|
|
108
108
|
"zod-validation-error": "^5.0.0",
|
|
109
109
|
"zustand": "^5.0.13",
|
|
110
|
-
"@bike4mind/fab-pipeline": "0.2.
|
|
111
|
-
"@bike4mind/llm-adapters": "0.3.
|
|
110
|
+
"@bike4mind/fab-pipeline": "0.2.9",
|
|
111
|
+
"@bike4mind/llm-adapters": "0.3.4",
|
|
112
112
|
"@bike4mind/observability": "0.1.0"
|
|
113
113
|
},
|
|
114
114
|
"devDependencies": {
|
|
@@ -124,11 +124,11 @@
|
|
|
124
124
|
"tsx": "^4.22.3",
|
|
125
125
|
"typescript": "^5.9.3",
|
|
126
126
|
"vitest": "^4.1.7",
|
|
127
|
-
"@bike4mind/agents": "0.11.
|
|
128
|
-
"@bike4mind/common": "2.
|
|
129
|
-
"@bike4mind/mcp": "1.37.
|
|
130
|
-
"@bike4mind/services": "2.92.
|
|
131
|
-
"@bike4mind/utils": "2.23.
|
|
127
|
+
"@bike4mind/agents": "0.11.8",
|
|
128
|
+
"@bike4mind/common": "2.107.0",
|
|
129
|
+
"@bike4mind/mcp": "1.37.22",
|
|
130
|
+
"@bike4mind/services": "2.92.2",
|
|
131
|
+
"@bike4mind/utils": "2.23.10"
|
|
132
132
|
},
|
|
133
133
|
"optionalDependencies": {
|
|
134
134
|
"@vscode/ripgrep": "^1.18.0"
|