@bike4mind/cli 0.2.30-feat-cli-scrollable-command-autocomplete.19204 → 0.2.30-feat-cli-websocket-streaming.19243

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.
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  CurationArtifactType
4
- } from "./chunk-C3K7SH2V.js";
4
+ } from "./chunk-55ZDRTGQ.js";
5
5
 
6
6
  // ../../b4m-core/packages/services/dist/src/notebookCurationService/artifactExtractor.js
7
7
  var ARTIFACT_TAG_REGEX = /<artifact\s+(.*?)>([\s\S]*?)<\/artifact>/gi;
@@ -1181,6 +1181,29 @@ var VoiceSessionSendTranscriptAction = z10.object({
1181
1181
  conversationItemId: z10.string(),
1182
1182
  timestamp: z10.coerce.date().optional()
1183
1183
  });
1184
+ var CliCompletionRequestAction = z10.object({
1185
+ action: z10.literal("cli_completion_request"),
1186
+ accessToken: z10.string(),
1187
+ requestId: z10.string().uuid(),
1188
+ model: z10.string(),
1189
+ messages: z10.array(z10.object({
1190
+ role: z10.enum(["user", "assistant", "system"]),
1191
+ content: z10.union([z10.string(), z10.array(z10.unknown())])
1192
+ })),
1193
+ options: z10.object({
1194
+ temperature: z10.number().optional(),
1195
+ maxTokens: z10.number().optional(),
1196
+ stream: z10.boolean().optional(),
1197
+ tools: z10.array(z10.unknown()).optional()
1198
+ }).optional()
1199
+ });
1200
+ var CliToolRequestAction = z10.object({
1201
+ action: z10.literal("cli_tool_request"),
1202
+ accessToken: z10.string(),
1203
+ requestId: z10.string().uuid(),
1204
+ toolName: z10.string(),
1205
+ input: z10.record(z10.unknown())
1206
+ });
1184
1207
  var VoiceSessionEndedAction = z10.object({
1185
1208
  action: z10.literal("voice_session_ended"),
1186
1209
  userId: z10.string(),
@@ -1469,6 +1492,36 @@ var VoiceCreditsExhaustedAction = z10.object({
1469
1492
  creditsUsed: z10.number(),
1470
1493
  clientId: z10.string().optional()
1471
1494
  });
1495
+ var CliCompletionChunkAction = z10.object({
1496
+ action: z10.literal("cli_completion_chunk"),
1497
+ requestId: z10.string(),
1498
+ chunk: z10.object({
1499
+ type: z10.enum(["content", "tool_use"]),
1500
+ text: z10.string(),
1501
+ tools: z10.array(z10.unknown()).optional(),
1502
+ usage: z10.object({
1503
+ inputTokens: z10.number().optional(),
1504
+ outputTokens: z10.number().optional()
1505
+ }).optional(),
1506
+ thinking: z10.array(z10.unknown()).optional()
1507
+ })
1508
+ });
1509
+ var CliCompletionDoneAction = z10.object({
1510
+ action: z10.literal("cli_completion_done"),
1511
+ requestId: z10.string()
1512
+ });
1513
+ var CliCompletionErrorAction = z10.object({
1514
+ action: z10.literal("cli_completion_error"),
1515
+ requestId: z10.string(),
1516
+ error: z10.string()
1517
+ });
1518
+ var CliToolResponseAction = z10.object({
1519
+ action: z10.literal("cli_tool_response"),
1520
+ requestId: z10.string(),
1521
+ success: z10.boolean(),
1522
+ content: z10.unknown().optional(),
1523
+ error: z10.string().optional()
1524
+ });
1472
1525
  var SessionCreatedAction = shareableDocumentSchema.extend({
1473
1526
  action: z10.literal("session.created"),
1474
1527
  id: z10.string(),
@@ -1503,7 +1556,9 @@ var MessageDataToServer = z10.discriminatedUnion("action", [
1503
1556
  DataUnsubscribeRequestAction,
1504
1557
  HeartbeatAction,
1505
1558
  VoiceSessionSendTranscriptAction,
1506
- VoiceSessionEndedAction
1559
+ VoiceSessionEndedAction,
1560
+ CliCompletionRequestAction,
1561
+ CliToolRequestAction
1507
1562
  ]);
1508
1563
  var MessageDataToClient = z10.discriminatedUnion("action", [
1509
1564
  DataSubscriptionUpdateAction,
@@ -1529,7 +1584,11 @@ var MessageDataToClient = z10.discriminatedUnion("action", [
1529
1584
  PiHistoryCompleteAction,
1530
1585
  PiHistoryErrorAction,
1531
1586
  SessionCreatedAction,
1532
- VoiceCreditsExhaustedAction
1587
+ VoiceCreditsExhaustedAction,
1588
+ CliCompletionChunkAction,
1589
+ CliCompletionDoneAction,
1590
+ CliCompletionErrorAction,
1591
+ CliToolResponseAction
1533
1592
  ]);
1534
1593
 
1535
1594
  // ../../b4m-core/packages/common/dist/src/schemas/cliCompletions.js
@@ -8724,6 +8783,8 @@ export {
8724
8783
  DataUnsubscribeRequestAction,
8725
8784
  HeartbeatAction,
8726
8785
  VoiceSessionSendTranscriptAction,
8786
+ CliCompletionRequestAction,
8787
+ CliToolRequestAction,
8727
8788
  VoiceSessionEndedAction,
8728
8789
  DataSubscriptionUpdateAction,
8729
8790
  LLMStatusUpdateAction,
@@ -8748,6 +8809,10 @@ export {
8748
8809
  ImportHistoryJobProgressUpdateAction,
8749
8810
  ResearchModeStreamAction,
8750
8811
  VoiceCreditsExhaustedAction,
8812
+ CliCompletionChunkAction,
8813
+ CliCompletionDoneAction,
8814
+ CliCompletionErrorAction,
8815
+ CliToolResponseAction,
8751
8816
  SessionCreatedAction,
8752
8817
  MessageDataToServer,
8753
8818
  MessageDataToClient,
@@ -16,7 +16,7 @@ import {
16
16
  dayjsConfig_default,
17
17
  extractSnippetMeta,
18
18
  settingsMap
19
- } from "./chunk-C3K7SH2V.js";
19
+ } from "./chunk-55ZDRTGQ.js";
20
20
  import {
21
21
  Logger
22
22
  } from "./chunk-OCYRD7D6.js";
@@ -2493,7 +2493,13 @@ var AnthropicBackend = class {
2493
2493
  const rawTools = options.tools;
2494
2494
  const normalizedTools = Array.isArray(rawTools) ? rawTools : rawTools ? [rawTools] : void 0;
2495
2495
  options.tools = normalizedTools;
2496
- const system = this.consolidateSystemMessages(messages);
2496
+ let system = this.consolidateSystemMessages(messages);
2497
+ if (system) {
2498
+ system += `
2499
+ IMPORTANT! Only when someone asks, remember that you are specifically the ${model} model.`;
2500
+ } else {
2501
+ system = `IMPORTANT! Only when someone asks, remember that you are specifically the ${model} model.`;
2502
+ }
2497
2503
  let filteredMessages = ensureToolPairingIntegrity(this.filterRelevantMessages(messages), this.logger);
2498
2504
  const countToolBlocks = (msgs) => {
2499
2505
  let useCount = 0;
@@ -3852,7 +3858,10 @@ var AnthropicBedrockBackend = class extends BaseBedrockBackend {
3852
3858
  }
3853
3859
  return { ...m, content: "" };
3854
3860
  }).filter((m) => m.content !== "" && (Array.isArray(m.content) ? m.content.length > 0 : true));
3855
- const systemMessage = messages.filter((m) => m.role === "system" && m.content).map((m) => typeof m.content === "string" ? m.content : JSON.stringify(m.content)).join("\n");
3861
+ let systemMessage = messages.filter((m) => m.role === "system" && m.content).map((m) => typeof m.content === "string" ? m.content : JSON.stringify(m.content)).join("\n");
3862
+ const modelIdentity = `IMPORTANT! Only when someone asks, remember that you are specifically the ${model} model.`;
3863
+ systemMessage = systemMessage ? `${systemMessage}
3864
+ ${modelIdentity}` : modelIdentity;
3856
3865
  console.log(`[AnthropicBedrockBackend] Preparing payload for model: ${model}`);
3857
3866
  const hasVendorPrefix = model.includes(":") || model.startsWith("global.") || model.startsWith("us.");
3858
3867
  const modelId = hasVendorPrefix ? model : `anthropic.${model}`;
@@ -49,7 +49,16 @@ var useCliStore = create((set) => ({
49
49
  // Input state (for Ctrl+C clearing)
50
50
  inputValue: "",
51
51
  setInputValue: (value) => set({ inputValue: value }),
52
- clearInput: () => set({ inputValue: "" }),
52
+ clearInput: () => set({ inputValue: "", pastedContent: null, pastedLineCount: 0 }),
53
+ // Paste state
54
+ pastedContent: null,
55
+ pastedLineCount: 0,
56
+ setPastedContent: (content, lineCount) => set({
57
+ pastedContent: content,
58
+ pastedLineCount: lineCount,
59
+ inputValue: content
60
+ }),
61
+ clearPaste: () => set({ pastedContent: null, pastedLineCount: 0, inputValue: "" }),
53
62
  // Permission prompt queue
54
63
  permissionPrompt: null,
55
64
  permissionQueue: [],
@@ -7,11 +7,11 @@ import {
7
7
  getSettingsMap,
8
8
  getSettingsValue,
9
9
  secureParameters
10
- } from "./chunk-5HA4JEOV.js";
10
+ } from "./chunk-7EEPLDUG.js";
11
11
  import {
12
12
  KnowledgeType,
13
13
  SupportedFabFileMimeTypes
14
- } from "./chunk-C3K7SH2V.js";
14
+ } from "./chunk-55ZDRTGQ.js";
15
15
 
16
16
  // ../../b4m-core/packages/services/dist/src/fabFileService/create.js
17
17
  import { z } from "zod";
@@ -6,12 +6,12 @@ import {
6
6
  getSettingsByNames,
7
7
  obfuscateApiKey,
8
8
  secureParameters
9
- } from "./chunk-5HA4JEOV.js";
9
+ } from "./chunk-7EEPLDUG.js";
10
10
  import {
11
11
  ApiKeyType,
12
12
  MementoTier,
13
13
  isSupportedEmbeddingModel
14
- } from "./chunk-C3K7SH2V.js";
14
+ } from "./chunk-55ZDRTGQ.js";
15
15
 
16
16
  // ../../b4m-core/packages/services/dist/src/apiKeyService/get.js
17
17
  import { z } from "zod";
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  BadRequestError,
4
4
  secureParameters
5
- } from "./chunk-5HA4JEOV.js";
5
+ } from "./chunk-7EEPLDUG.js";
6
6
  import {
7
7
  CompletionApiUsageTransaction,
8
8
  GenericCreditDeductTransaction,
@@ -12,7 +12,7 @@ import {
12
12
  TextGenerationUsageTransaction,
13
13
  TransferCreditTransaction,
14
14
  VideoGenerationUsageTransaction
15
- } from "./chunk-C3K7SH2V.js";
15
+ } from "./chunk-55ZDRTGQ.js";
16
16
 
17
17
  // ../../b4m-core/packages/services/dist/src/creditService/subtractCredits.js
18
18
  import { z } from "zod";
@@ -2,9 +2,9 @@
2
2
  import {
3
3
  createFabFile,
4
4
  createFabFileSchema
5
- } from "./chunk-B7Z2QQMD.js";
6
- import "./chunk-5HA4JEOV.js";
7
- import "./chunk-C3K7SH2V.js";
5
+ } from "./chunk-K5IURWQT.js";
6
+ import "./chunk-7EEPLDUG.js";
7
+ import "./chunk-55ZDRTGQ.js";
8
8
  import "./chunk-OCYRD7D6.js";
9
9
  export {
10
10
  createFabFile,
package/dist/index.js CHANGED
@@ -5,7 +5,7 @@ import {
5
5
  getEffectiveApiKey,
6
6
  getOpenWeatherKey,
7
7
  getSerperKey
8
- } from "./chunk-65DLNQY2.js";
8
+ } from "./chunk-KBRR7H2K.js";
9
9
  import "./chunk-RUI6HNLO.js";
10
10
  import {
11
11
  ConfigStore,
@@ -14,9 +14,9 @@ import {
14
14
  import {
15
15
  selectActiveBackgroundAgents,
16
16
  useCliStore
17
- } from "./chunk-TVW4ZESU.js";
18
- import "./chunk-BYLWQZ5S.js";
19
- import "./chunk-B7Z2QQMD.js";
17
+ } from "./chunk-BYXFQJYT.js";
18
+ import "./chunk-XXYKIX5X.js";
19
+ import "./chunk-K5IURWQT.js";
20
20
  import {
21
21
  BFLImageService,
22
22
  BaseStorage,
@@ -28,7 +28,7 @@ import {
28
28
  OpenAIBackend,
29
29
  OpenAIImageService,
30
30
  XAIImageService
31
- } from "./chunk-5HA4JEOV.js";
31
+ } from "./chunk-7EEPLDUG.js";
32
32
  import {
33
33
  AiEvents,
34
34
  ApiKeyEvents,
@@ -84,7 +84,7 @@ import {
84
84
  XAI_IMAGE_MODELS,
85
85
  b4mLLMTools,
86
86
  getMcpProviderMetadata
87
- } from "./chunk-C3K7SH2V.js";
87
+ } from "./chunk-55ZDRTGQ.js";
88
88
  import {
89
89
  Logger
90
90
  } from "./chunk-OCYRD7D6.js";
@@ -94,7 +94,7 @@ import React21, { useState as useState10, useEffect as useEffect7, useCallback a
94
94
  import { render, Box as Box20, Text as Text20, useApp, useInput as useInput9 } from "ink";
95
95
  import { execSync } from "child_process";
96
96
  import { randomBytes as randomBytes5 } from "crypto";
97
- import { v4 as uuidv411 } from "uuid";
97
+ import { v4 as uuidv413 } from "uuid";
98
98
 
99
99
  // src/components/App.tsx
100
100
  import React15, { useState as useState6, useEffect as useEffect5 } from "react";
@@ -120,10 +120,22 @@ import { Box as Box4, Text as Text5, useInput as useInput2 } from "ink";
120
120
  // src/components/CustomTextInput.tsx
121
121
  import React2, { useEffect, useRef, useState } from "react";
122
122
  import { Text as Text2, useInput } from "ink";
123
+
124
+ // src/config/constants.ts
125
+ var USAGE_DAYS = 30;
126
+ var MODEL_NAME_COLUMN_WIDTH = 18;
127
+ var USAGE_CACHE_TTL = 5 * 60 * 1e3;
128
+ var PASTE_LINE_THRESHOLD = 5;
129
+ var MAX_PASTE_SIZE = 5e5;
130
+ var IMAGE_DETECTION_MAX_LENGTH = 500;
131
+
132
+ // src/components/CustomTextInput.tsx
123
133
  function CustomTextInput({
124
134
  value,
125
135
  onChange,
126
136
  onSubmit,
137
+ onPaste,
138
+ pasteIndicator,
127
139
  placeholder = "",
128
140
  showCursor = true,
129
141
  disabled = false
@@ -275,6 +287,11 @@ function CustomTextInput({
275
287
  }
276
288
  }
277
289
  if (key.backspace || key.delete) {
290
+ if (pasteIndicator) {
291
+ onChange("");
292
+ setCursorOffset(0);
293
+ return;
294
+ }
278
295
  if (cursorOffset > 0) {
279
296
  const newValue = value.slice(0, cursorOffset - 1) + value.slice(cursorOffset);
280
297
  onChange(newValue);
@@ -291,6 +308,20 @@ function CustomTextInput({
291
308
  return;
292
309
  }
293
310
  if (!key.ctrl && !key.meta && input.length > 0) {
311
+ if (input.length > 1 && onPaste) {
312
+ const normalized = input.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
313
+ const lineCount = normalized.split("\n").length;
314
+ if (lineCount >= PASTE_LINE_THRESHOLD) {
315
+ onPaste(normalized);
316
+ return;
317
+ }
318
+ }
319
+ if (pasteIndicator) {
320
+ const sanitizedInput2 = input === "\r" ? "\n" : input;
321
+ onChange(sanitizedInput2);
322
+ setCursorOffset(sanitizedInput2.length);
323
+ return;
324
+ }
294
325
  const sanitizedInput = input === "\r" ? "\n" : input;
295
326
  const newValue = value.slice(0, cursorOffset) + sanitizedInput + value.slice(cursorOffset);
296
327
  onChange(newValue);
@@ -299,6 +330,9 @@ function CustomTextInput({
299
330
  },
300
331
  { isActive: !disabled }
301
332
  );
333
+ if (pasteIndicator) {
334
+ return /* @__PURE__ */ React2.createElement(Text2, null, /* @__PURE__ */ React2.createElement(Text2, { color: "yellow" }, pasteIndicator), showCursor && /* @__PURE__ */ React2.createElement(Text2, { inverse: true }, " "));
335
+ }
302
336
  const hasValue = value.length > 0;
303
337
  if (!hasValue) {
304
338
  return /* @__PURE__ */ React2.createElement(Text2, null, showCursor ? /* @__PURE__ */ React2.createElement(React2.Fragment, null, /* @__PURE__ */ React2.createElement(Text2, { inverse: true }, " "), /* @__PURE__ */ React2.createElement(Text2, { dimColor: true }, placeholder)) : /* @__PURE__ */ React2.createElement(Text2, { dimColor: true }, placeholder));
@@ -316,23 +350,9 @@ function CommandAutocomplete({ commands, selectedIndex }) {
316
350
  if (commands.length === 0) {
317
351
  return /* @__PURE__ */ React3.createElement(Box2, { marginLeft: 2, marginTop: 1 }, /* @__PURE__ */ React3.createElement(Text3, { dimColor: true }, "No matching commands"));
318
352
  }
319
- const VIEWPORT_SIZE = 6;
320
- const totalCommands = commands.length;
321
- let startIndex = 0;
322
- let endIndex = Math.min(VIEWPORT_SIZE, totalCommands);
323
- if (totalCommands > VIEWPORT_SIZE) {
324
- const halfViewport = Math.floor(VIEWPORT_SIZE / 2);
325
- startIndex = Math.max(0, selectedIndex - halfViewport);
326
- endIndex = Math.min(totalCommands, startIndex + VIEWPORT_SIZE);
327
- if (endIndex === totalCommands) {
328
- startIndex = Math.max(0, totalCommands - VIEWPORT_SIZE);
329
- }
330
- }
331
- const visibleCommands = commands.slice(startIndex, endIndex);
332
- return /* @__PURE__ */ React3.createElement(Box2, { flexDirection: "column", marginLeft: 2, marginTop: 1 }, /* @__PURE__ */ React3.createElement(Box2, { marginBottom: 1 }, /* @__PURE__ */ React3.createElement(Text3, { dimColor: true }, totalCommands === 1 ? "1 command" : `${totalCommands} commands`, totalCommands > VIEWPORT_SIZE && ` (${selectedIndex + 1}/${totalCommands})`, " \u2022 Use \u2191\u2193 to navigate, Enter to select")), visibleCommands.map((cmd, viewportIndex) => {
333
- const actualIndex = startIndex + viewportIndex;
353
+ return /* @__PURE__ */ React3.createElement(Box2, { flexDirection: "column", marginLeft: 2, marginTop: 1 }, /* @__PURE__ */ React3.createElement(Box2, { marginBottom: 1 }, /* @__PURE__ */ React3.createElement(Text3, { dimColor: true }, commands.length === 1 ? "1 command" : `${commands.length} commands`, " \u2022 Use \u2191\u2193 to navigate, Enter to select")), commands.map((cmd, index) => {
334
354
  const args = cmd.args ? ` ${cmd.args}` : "";
335
- const isSelected = actualIndex === selectedIndex;
355
+ const isSelected = index === selectedIndex;
336
356
  const sourceIcon = cmd.source === "global" ? "\u{1F3E0} " : cmd.source === "project" ? "\u{1F4C1} " : cmd.source === "built-in" ? "\u{1F527} " : "";
337
357
  return /* @__PURE__ */ React3.createElement(Box2, { key: cmd.name, marginLeft: 1 }, /* @__PURE__ */ React3.createElement(Text3, { color: isSelected ? "cyan" : void 0, bold: isSelected }, isSelected ? "\u25B8 " : " ", sourceIcon, "/", cmd.name, args, " - ", cmd.description));
338
358
  }));
@@ -1055,8 +1075,11 @@ function InputPrompt({
1055
1075
  onBashModeChange
1056
1076
  }) {
1057
1077
  const value = useCliStore((state) => state.inputValue);
1058
- const setInputValue = useCliStore((state) => state.setInputValue);
1059
- const setValue = setInputValue;
1078
+ const setValue = useCliStore((state) => state.setInputValue);
1079
+ const pastedContent = useCliStore((state) => state.pastedContent);
1080
+ const pastedLineCount = useCliStore((state) => state.pastedLineCount);
1081
+ const setPastedContent = useCliStore((state) => state.setPastedContent);
1082
+ const clearPaste = useCliStore((state) => state.clearPaste);
1060
1083
  const [selectedIndex, setSelectedIndex] = useState3(0);
1061
1084
  const [historyIndex, setHistoryIndex] = useState3(-1);
1062
1085
  const [tempInput, setTempInput] = useState3("");
@@ -1073,8 +1096,8 @@ function InputPrompt({
1073
1096
  useEffect3(() => {
1074
1097
  onBashModeChange?.(isBashMode);
1075
1098
  }, [isBashMode, onBashModeChange]);
1076
- const commandQuery = value.startsWith("/") ? value.slice(1) : "";
1077
- const shouldShowCommandAutocomplete = value.startsWith("/") && !disabled && !fileAutocomplete?.active && !looksLikeFilePath(value) && !commandQuery.includes(" ");
1099
+ const shouldShowCommandAutocomplete = value.startsWith("/") && !disabled && !fileAutocomplete?.active && !looksLikeFilePath(value) && !pastedContent;
1100
+ const commandQuery = shouldShowCommandAutocomplete ? value.slice(1) : "";
1078
1101
  const filteredCommands = useMemo(() => {
1079
1102
  if (!shouldShowCommandAutocomplete) return [];
1080
1103
  return searchCommands(commandQuery, commands);
@@ -1180,7 +1203,14 @@ function InputPrompt({
1180
1203
  }
1181
1204
  };
1182
1205
  const handleSubmit = (input) => {
1183
- if (disabled || !input.trim()) return;
1206
+ if (disabled) return;
1207
+ if (pastedContent) {
1208
+ const fullContent = pastedContent;
1209
+ clearPaste();
1210
+ onSubmit(fullContent);
1211
+ return;
1212
+ }
1213
+ if (!input.trim()) return;
1184
1214
  if (fileAutocomplete?.active && filteredFiles.length > 0) {
1185
1215
  const selectedFile = filteredFiles[fileSelectedIndex];
1186
1216
  if (selectedFile) {
@@ -1216,13 +1246,23 @@ function InputPrompt({
1216
1246
  setHistoryIndex(-1);
1217
1247
  setFileAutocomplete(null);
1218
1248
  };
1249
+ const handlePaste = (content) => {
1250
+ const truncated = content.length > MAX_PASTE_SIZE ? content.slice(0, MAX_PASTE_SIZE) : content;
1251
+ const lineCount = truncated.split("\n").length;
1252
+ setPastedContent(truncated, lineCount);
1253
+ };
1219
1254
  const handleChange = async (newValue) => {
1220
- if (ImageInputDetector.containsImageData(newValue)) {
1221
- const imageEvent = ImageInputDetector.extractImageData(newValue);
1222
- if (imageEvent && onImageDetected) {
1223
- const placeholder = await onImageDetected(imageEvent.data);
1224
- setValue(`${placeholder} `);
1225
- return;
1255
+ if (pastedContent) {
1256
+ clearPaste();
1257
+ }
1258
+ if (newValue.length <= IMAGE_DETECTION_MAX_LENGTH) {
1259
+ if (ImageInputDetector.containsImageData(newValue)) {
1260
+ const imageEvent = ImageInputDetector.extractImageData(newValue);
1261
+ if (imageEvent && onImageDetected) {
1262
+ const placeholder = await onImageDetected(imageEvent.data);
1263
+ setValue(`${placeholder} `);
1264
+ return;
1265
+ }
1226
1266
  }
1227
1267
  }
1228
1268
  setValue(newValue);
@@ -1253,6 +1293,8 @@ function InputPrompt({
1253
1293
  value,
1254
1294
  onChange: handleChange,
1255
1295
  onSubmit: handleSubmit,
1296
+ onPaste: handlePaste,
1297
+ pasteIndicator: pastedContent ? `[pasted +${pastedLineCount} lines]` : null,
1256
1298
  placeholder: getPlaceholder(),
1257
1299
  showCursor: !disabled,
1258
1300
  disabled
@@ -12495,6 +12537,10 @@ var ServerToolExecutor = class {
12495
12537
  };
12496
12538
 
12497
12539
  // src/llm/ToolRouter.ts
12540
+ var wsToolExecutor = null;
12541
+ function setWebSocketToolExecutor(executor) {
12542
+ wsToolExecutor = executor;
12543
+ }
12498
12544
  var SERVER_TOOLS = ["weather_info", "web_search", "web_fetch"];
12499
12545
  var LOCAL_TOOLS = [
12500
12546
  "file_read",
@@ -12517,7 +12563,15 @@ function isLocalTool(toolName) {
12517
12563
  }
12518
12564
  async function executeTool(toolName, input, apiClient, localToolFn) {
12519
12565
  if (isServerTool(toolName)) {
12520
- logger.debug(`[ToolRouter] Routing ${toolName} to server`);
12566
+ if (wsToolExecutor) {
12567
+ logger.debug(`[ToolRouter] Routing ${toolName} to server via WebSocket`);
12568
+ const result = await wsToolExecutor.execute(toolName, input);
12569
+ if (!result.success) {
12570
+ return `Error executing ${toolName}: ${result.error || "Tool execution failed"}`;
12571
+ }
12572
+ return typeof result.content === "string" ? result.content : JSON.stringify(result.content ?? "");
12573
+ }
12574
+ logger.debug(`[ToolRouter] Routing ${toolName} to server via HTTP`);
12521
12575
  const executor = new ServerToolExecutor(apiClient);
12522
12576
  return await executor.executeTool(toolName, input);
12523
12577
  } else if (isLocalTool(toolName)) {
@@ -12778,7 +12832,7 @@ function wrapToolWithPermission(tool, permissionManager, showPermissionPrompt, a
12778
12832
  agentContext.observationQueue.push({ toolName, result: result2 });
12779
12833
  return result2;
12780
12834
  }
12781
- const { useCliStore: useCliStore2 } = await import("./store-FU6NDC2W.js");
12835
+ const { useCliStore: useCliStore2 } = await import("./store-K5MB3SE7.js");
12782
12836
  if (useCliStore2.getState().autoAcceptEdits) {
12783
12837
  const result2 = await executeTool(toolName, args, apiClient, originalFn);
12784
12838
  agentContext.observationQueue.push({ toolName, result: result2 });
@@ -14479,6 +14533,173 @@ var ServerLlmBackend = class {
14479
14533
  }
14480
14534
  };
14481
14535
 
14536
+ // src/llm/WebSocketLlmBackend.ts
14537
+ import { v4 as uuidv411 } from "uuid";
14538
+ function stripThinkingBlocks2(text) {
14539
+ return text.replace(/<think>[\s\S]*?<\/think>/g, "").trim();
14540
+ }
14541
+ var WebSocketLlmBackend = class {
14542
+ constructor(options) {
14543
+ this.wsManager = options.wsManager;
14544
+ this.apiClient = options.apiClient;
14545
+ this.currentModel = options.model;
14546
+ this.tokenGetter = options.tokenGetter;
14547
+ this.wsCompletionUrl = options.wsCompletionUrl;
14548
+ }
14549
+ /**
14550
+ * Send completion request via HTTP POST, receive streaming response via WebSocket.
14551
+ * Collects all streamed chunks, then calls callback once at completion
14552
+ * with the full accumulated content.
14553
+ */
14554
+ async complete(model, messages, options, callback) {
14555
+ logger.debug(`[WebSocketLlmBackend] Starting complete() with model: ${model}`);
14556
+ if (options.abortSignal?.aborted) {
14557
+ logger.debug("[WebSocketLlmBackend] Request aborted before start");
14558
+ return;
14559
+ }
14560
+ if (!this.wsManager.isConnected) {
14561
+ throw new Error("WebSocket is not connected");
14562
+ }
14563
+ const requestId = uuidv411();
14564
+ return new Promise((resolve3, reject) => {
14565
+ const isVerbose = process.env.B4M_VERBOSE === "1";
14566
+ const isUltraVerbose = process.env.B4M_DEBUG_STREAM === "1";
14567
+ const streamLogger = new StreamLogger(logger, "WebSocketLlmBackend", isVerbose, isUltraVerbose);
14568
+ streamLogger.streamStart();
14569
+ let eventCount = 0;
14570
+ let accumulatedText = "";
14571
+ let lastUsageInfo = {};
14572
+ let toolsUsed = [];
14573
+ let thinkingBlocks = [];
14574
+ let settled = false;
14575
+ const settle = (action) => {
14576
+ if (settled) return;
14577
+ settled = true;
14578
+ this.wsManager.offRequest(requestId);
14579
+ this.wsManager.offDisconnect(onDisconnect);
14580
+ action();
14581
+ };
14582
+ const settleResolve = () => settle(() => resolve3());
14583
+ const settleReject = (err) => settle(() => reject(err));
14584
+ const onDisconnect = () => {
14585
+ logger.debug("[WebSocketLlmBackend] Connection dropped during completion");
14586
+ settleReject(new Error("WebSocket connection lost during completion"));
14587
+ };
14588
+ this.wsManager.onDisconnect(onDisconnect);
14589
+ if (options.abortSignal) {
14590
+ if (options.abortSignal.aborted) {
14591
+ settleResolve();
14592
+ return;
14593
+ }
14594
+ options.abortSignal.addEventListener(
14595
+ "abort",
14596
+ () => {
14597
+ logger.debug("[WebSocketLlmBackend] Abort signal received");
14598
+ settleResolve();
14599
+ },
14600
+ { once: true }
14601
+ );
14602
+ }
14603
+ const updateUsage = (usage) => {
14604
+ if (usage) {
14605
+ lastUsageInfo = { inputTokens: usage.inputTokens, outputTokens: usage.outputTokens };
14606
+ }
14607
+ };
14608
+ this.wsManager.onRequest(requestId, (message) => {
14609
+ if (options.abortSignal?.aborted) return;
14610
+ const action = message.action;
14611
+ if (action === "cli_completion_chunk") {
14612
+ eventCount++;
14613
+ const chunk = message.chunk;
14614
+ streamLogger.onEvent(eventCount, JSON.stringify(chunk));
14615
+ const textChunk = chunk.text || "";
14616
+ if (textChunk) accumulatedText += textChunk;
14617
+ updateUsage(chunk.usage);
14618
+ if (chunk.type === "content") {
14619
+ streamLogger.onContent(eventCount, textChunk, accumulatedText);
14620
+ } else if (chunk.type === "tool_use") {
14621
+ streamLogger.onCriticalEvent(eventCount, "TOOL_USE", `tools: ${chunk.tools?.length}`);
14622
+ if (chunk.tools && chunk.tools.length > 0) toolsUsed = chunk.tools;
14623
+ if (chunk.thinking && chunk.thinking.length > 0) thinkingBlocks = chunk.thinking;
14624
+ }
14625
+ } else if (action === "cli_completion_done") {
14626
+ streamLogger.streamComplete(accumulatedText);
14627
+ const cleanedText = stripThinkingBlocks2(accumulatedText);
14628
+ if (!cleanedText && toolsUsed.length === 0) {
14629
+ settleResolve();
14630
+ return;
14631
+ }
14632
+ const info = {
14633
+ ...lastUsageInfo,
14634
+ ...toolsUsed.length > 0 && { toolsUsed },
14635
+ ...thinkingBlocks.length > 0 && { thinking: thinkingBlocks }
14636
+ };
14637
+ callback([cleanedText], info).then(() => settleResolve()).catch((err) => settleReject(err));
14638
+ } else if (action === "cli_completion_error") {
14639
+ const errorMsg = message.error || "Server error";
14640
+ streamLogger.onCriticalEvent(eventCount, "ERROR", errorMsg);
14641
+ settleReject(new Error(errorMsg));
14642
+ }
14643
+ });
14644
+ const axiosInstance = this.apiClient.getAxiosInstance();
14645
+ axiosInstance.post(
14646
+ this.wsCompletionUrl,
14647
+ {
14648
+ requestId,
14649
+ model,
14650
+ messages,
14651
+ options: {
14652
+ temperature: options.temperature,
14653
+ maxTokens: options.maxTokens,
14654
+ stream: true,
14655
+ tools: options.tools || []
14656
+ }
14657
+ },
14658
+ { signal: options.abortSignal }
14659
+ ).catch((err) => {
14660
+ const msg = err instanceof Error ? err.message : String(err);
14661
+ settleReject(new Error(`HTTP request failed: ${msg}`));
14662
+ });
14663
+ });
14664
+ }
14665
+ /**
14666
+ * Get available models from server (REST call, not streaming).
14667
+ * Delegates to ApiClient -- same as ServerLlmBackend.
14668
+ */
14669
+ async getModelInfo() {
14670
+ try {
14671
+ logger.debug("[WebSocketLlmBackend] Fetching models from /api/models");
14672
+ const response = await this.apiClient.get("/api/models");
14673
+ if (!response || typeof response !== "object" || !Array.isArray(response.models)) {
14674
+ logger.warn("[WebSocketLlmBackend] Invalid API response format, using fallback models");
14675
+ return this.getFallbackModels();
14676
+ }
14677
+ const filteredModels = response.models.filter(
14678
+ (model) => model.type === "text" && model.supportsTools === true
14679
+ );
14680
+ if (filteredModels.length === 0) {
14681
+ logger.warn("[WebSocketLlmBackend] No CLI-compatible models found, using fallback");
14682
+ return this.getFallbackModels();
14683
+ }
14684
+ logger.debug(`[WebSocketLlmBackend] Loaded ${filteredModels.length} models`);
14685
+ return filteredModels;
14686
+ } catch (error) {
14687
+ logger.warn(
14688
+ `[WebSocketLlmBackend] Failed to fetch models: ${error instanceof Error ? error.message : String(error)}`
14689
+ );
14690
+ return this.getFallbackModels();
14691
+ }
14692
+ }
14693
+ getFallbackModels() {
14694
+ return [
14695
+ { id: "claude-sonnet-4-5-20250929", name: "Claude 4.5 Sonnet" },
14696
+ { id: "claude-3-5-haiku-20241022", name: "Claude 3.5 Haiku" },
14697
+ { id: "gpt-4o", name: "GPT-4o" },
14698
+ { id: "gpt-4o-mini", name: "GPT-4o Mini" }
14699
+ ];
14700
+ }
14701
+ };
14702
+
14482
14703
  // src/llm/NotifyingLlmBackend.ts
14483
14704
  var NotifyingLlmBackend = class {
14484
14705
  constructor(inner, backgroundManager) {
@@ -14513,6 +14734,253 @@ Please acknowledge these background agent results and incorporate them into your
14513
14734
  }
14514
14735
  };
14515
14736
 
14737
+ // src/ws/WebSocketConnectionManager.ts
14738
+ var WebSocketConnectionManager = class {
14739
+ constructor(wsUrl, getToken) {
14740
+ this.ws = null;
14741
+ this.heartbeatInterval = null;
14742
+ this.reconnectAttempts = 0;
14743
+ this.maxReconnectDelay = 3e4;
14744
+ this.handlers = /* @__PURE__ */ new Map();
14745
+ this.disconnectHandlers = /* @__PURE__ */ new Set();
14746
+ this.reconnectTimer = null;
14747
+ this.connected = false;
14748
+ this.connecting = false;
14749
+ this.closed = false;
14750
+ this.wsUrl = wsUrl;
14751
+ this.getToken = getToken;
14752
+ }
14753
+ /**
14754
+ * Connect to the WebSocket server.
14755
+ * Resolves when connection is established, rejects on failure.
14756
+ */
14757
+ async connect() {
14758
+ if (this.connected || this.connecting) return;
14759
+ this.connecting = true;
14760
+ const token = await this.getToken();
14761
+ if (!token) {
14762
+ this.connecting = false;
14763
+ throw new Error("No access token available for WebSocket connection");
14764
+ }
14765
+ return new Promise((resolve3, reject) => {
14766
+ logger.debug(`[WS] Connecting to ${this.wsUrl}...`);
14767
+ this.ws = new WebSocket(this.wsUrl, [`access_token.${token}`]);
14768
+ this.ws.onopen = () => {
14769
+ logger.debug("[WS] Connected");
14770
+ this.connected = true;
14771
+ this.connecting = false;
14772
+ this.reconnectAttempts = 0;
14773
+ this.startHeartbeat();
14774
+ resolve3();
14775
+ };
14776
+ this.ws.onmessage = (event) => {
14777
+ try {
14778
+ const data = typeof event.data === "string" ? event.data : event.data.toString();
14779
+ const message = JSON.parse(data);
14780
+ const requestId = message.requestId;
14781
+ if (requestId && this.handlers.has(requestId)) {
14782
+ this.handlers.get(requestId)(message);
14783
+ } else {
14784
+ logger.debug(`[WS] Unhandled message: ${message.action || "unknown"}`);
14785
+ }
14786
+ } catch (err) {
14787
+ logger.debug(`[WS] Failed to parse message: ${err}`);
14788
+ }
14789
+ };
14790
+ this.ws.onclose = () => {
14791
+ logger.debug("[WS] Connection closed");
14792
+ this.cleanup();
14793
+ this.notifyDisconnect();
14794
+ if (!this.closed) {
14795
+ this.scheduleReconnect();
14796
+ }
14797
+ };
14798
+ this.ws.onerror = (err) => {
14799
+ logger.debug(`[WS] Error: ${err}`);
14800
+ if (this.connecting) {
14801
+ this.connecting = false;
14802
+ this.connected = false;
14803
+ reject(new Error("WebSocket connection failed"));
14804
+ }
14805
+ };
14806
+ });
14807
+ }
14808
+ /** Whether the connection is currently established */
14809
+ get isConnected() {
14810
+ return this.connected;
14811
+ }
14812
+ /**
14813
+ * Send a JSON message over the WebSocket connection.
14814
+ */
14815
+ send(data) {
14816
+ if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
14817
+ throw new Error("WebSocket is not connected");
14818
+ }
14819
+ const payload = JSON.stringify(data);
14820
+ const sizeKB = (payload.length / 1024).toFixed(1);
14821
+ logger.debug(`[WS] Sending ${sizeKB} KB (action: ${data.action})`);
14822
+ if (payload.length > 32e3) {
14823
+ logger.warn(`[WS] Payload ${sizeKB} KB exceeds API Gateway 32 KB frame limit \u2014 connection will be closed`);
14824
+ }
14825
+ this.ws.send(payload);
14826
+ }
14827
+ /**
14828
+ * Register a handler for messages matching a specific requestId.
14829
+ */
14830
+ onRequest(requestId, handler) {
14831
+ this.handlers.set(requestId, handler);
14832
+ }
14833
+ /**
14834
+ * Remove a handler for a specific requestId.
14835
+ */
14836
+ offRequest(requestId) {
14837
+ this.handlers.delete(requestId);
14838
+ }
14839
+ /**
14840
+ * Register a handler that fires when the connection drops.
14841
+ */
14842
+ onDisconnect(handler) {
14843
+ this.disconnectHandlers.add(handler);
14844
+ }
14845
+ /**
14846
+ * Remove a disconnect handler.
14847
+ */
14848
+ offDisconnect(handler) {
14849
+ this.disconnectHandlers.delete(handler);
14850
+ }
14851
+ /**
14852
+ * Close the connection and stop all heartbeat/reconnect logic.
14853
+ */
14854
+ disconnect() {
14855
+ this.closed = true;
14856
+ this.cleanup();
14857
+ if (this.ws) {
14858
+ this.ws.close();
14859
+ this.ws = null;
14860
+ }
14861
+ this.handlers.clear();
14862
+ this.disconnectHandlers.clear();
14863
+ }
14864
+ startHeartbeat() {
14865
+ this.stopHeartbeat();
14866
+ this.heartbeatInterval = setInterval(
14867
+ () => {
14868
+ if (this.ws && this.ws.readyState === WebSocket.OPEN) {
14869
+ this.ws.send(JSON.stringify({ action: "heartbeat" }));
14870
+ logger.debug("[WS] Heartbeat sent");
14871
+ }
14872
+ },
14873
+ 5 * 60 * 1e3
14874
+ );
14875
+ }
14876
+ stopHeartbeat() {
14877
+ if (this.heartbeatInterval) {
14878
+ clearInterval(this.heartbeatInterval);
14879
+ this.heartbeatInterval = null;
14880
+ }
14881
+ }
14882
+ cleanup() {
14883
+ this.connected = false;
14884
+ this.connecting = false;
14885
+ this.stopHeartbeat();
14886
+ if (this.reconnectTimer) {
14887
+ clearTimeout(this.reconnectTimer);
14888
+ this.reconnectTimer = null;
14889
+ }
14890
+ }
14891
+ notifyDisconnect() {
14892
+ for (const handler of this.disconnectHandlers) {
14893
+ try {
14894
+ handler();
14895
+ } catch {
14896
+ }
14897
+ }
14898
+ }
14899
+ scheduleReconnect() {
14900
+ if (this.closed) return;
14901
+ this.reconnectAttempts++;
14902
+ const delay = Math.min(1e3 * Math.pow(2, this.reconnectAttempts - 1), this.maxReconnectDelay);
14903
+ logger.debug(`[WS] Reconnecting in ${delay}ms (attempt ${this.reconnectAttempts})`);
14904
+ this.reconnectTimer = setTimeout(async () => {
14905
+ this.reconnectTimer = null;
14906
+ if (this.closed) return;
14907
+ try {
14908
+ await this.connect();
14909
+ } catch {
14910
+ logger.debug("[WS] Reconnection failed");
14911
+ }
14912
+ }, delay);
14913
+ }
14914
+ };
14915
+
14916
+ // src/ws/WebSocketToolExecutor.ts
14917
+ import { v4 as uuidv412 } from "uuid";
14918
+ var WebSocketToolExecutor = class {
14919
+ constructor(wsManager, tokenGetter) {
14920
+ this.wsManager = wsManager;
14921
+ this.tokenGetter = tokenGetter;
14922
+ }
14923
+ /**
14924
+ * Execute a server-side tool via WebSocket.
14925
+ * Returns the tool result or throws on error.
14926
+ */
14927
+ async execute(toolName, input, abortSignal) {
14928
+ if (!this.wsManager.isConnected) {
14929
+ throw new Error("WebSocket is not connected");
14930
+ }
14931
+ const token = await this.tokenGetter();
14932
+ if (!token) {
14933
+ throw new Error("No access token available");
14934
+ }
14935
+ const requestId = uuidv412();
14936
+ return new Promise((resolve3, reject) => {
14937
+ let settled = false;
14938
+ const settle = (action) => {
14939
+ if (settled) return;
14940
+ settled = true;
14941
+ this.wsManager.offRequest(requestId);
14942
+ this.wsManager.offDisconnect(onDisconnect);
14943
+ action();
14944
+ };
14945
+ const settleResolve = (result) => settle(() => resolve3(result));
14946
+ const settleReject = (err) => settle(() => reject(err));
14947
+ const onDisconnect = () => {
14948
+ settleReject(new Error("WebSocket connection lost during tool execution"));
14949
+ };
14950
+ this.wsManager.onDisconnect(onDisconnect);
14951
+ if (abortSignal) {
14952
+ if (abortSignal.aborted) {
14953
+ settleReject(new Error("Tool execution aborted"));
14954
+ return;
14955
+ }
14956
+ abortSignal.addEventListener("abort", () => settleReject(new Error("Tool execution aborted")), {
14957
+ once: true
14958
+ });
14959
+ }
14960
+ this.wsManager.onRequest(requestId, (message) => {
14961
+ if (message.action === "cli_tool_response") {
14962
+ settleResolve({
14963
+ success: message.success,
14964
+ content: message.content,
14965
+ error: message.error
14966
+ });
14967
+ }
14968
+ });
14969
+ try {
14970
+ this.wsManager.send({
14971
+ action: "cli_tool_request",
14972
+ accessToken: token,
14973
+ requestId,
14974
+ toolName,
14975
+ input
14976
+ });
14977
+ } catch (err) {
14978
+ settleReject(err instanceof Error ? err : new Error(String(err)));
14979
+ }
14980
+ });
14981
+ }
14982
+ };
14983
+
14516
14984
  // src/auth/ApiClient.ts
14517
14985
  import axios11 from "axios";
14518
14986
  var ApiClient = class {
@@ -14650,7 +15118,7 @@ import { isAxiosError as isAxiosError2 } from "axios";
14650
15118
  // package.json
14651
15119
  var package_default = {
14652
15120
  name: "@bike4mind/cli",
14653
- version: "0.2.30-feat-cli-scrollable-command-autocomplete.19204+bf1e097e7",
15121
+ version: "0.2.30-feat-cli-websocket-streaming.19243+6a95a9d10",
14654
15122
  type: "module",
14655
15123
  description: "Interactive CLI tool for Bike4Mind with ReAct agents",
14656
15124
  license: "UNLICENSED",
@@ -14761,10 +15229,10 @@ var package_default = {
14761
15229
  },
14762
15230
  devDependencies: {
14763
15231
  "@bike4mind/agents": "0.1.0",
14764
- "@bike4mind/common": "2.51.1-feat-cli-scrollable-command-autocomplete.19204+bf1e097e7",
14765
- "@bike4mind/mcp": "1.30.1-feat-cli-scrollable-command-autocomplete.19204+bf1e097e7",
14766
- "@bike4mind/services": "2.49.1-feat-cli-scrollable-command-autocomplete.19204+bf1e097e7",
14767
- "@bike4mind/utils": "2.6.1-feat-cli-scrollable-command-autocomplete.19204+bf1e097e7",
15232
+ "@bike4mind/common": "2.51.1-feat-cli-websocket-streaming.19243+6a95a9d10",
15233
+ "@bike4mind/mcp": "1.30.1-feat-cli-websocket-streaming.19243+6a95a9d10",
15234
+ "@bike4mind/services": "2.49.1-feat-cli-websocket-streaming.19243+6a95a9d10",
15235
+ "@bike4mind/utils": "2.6.1-feat-cli-websocket-streaming.19243+6a95a9d10",
14768
15236
  "@types/better-sqlite3": "^7.6.13",
14769
15237
  "@types/diff": "^5.0.9",
14770
15238
  "@types/jsonwebtoken": "^9.0.4",
@@ -14785,14 +15253,9 @@ var package_default = {
14785
15253
  optionalDependencies: {
14786
15254
  "@vscode/ripgrep": "^1.17.0"
14787
15255
  },
14788
- gitHead: "bf1e097e7c47caaaa19b04b39a11fd1e509efab2"
15256
+ gitHead: "6a95a9d108b3eb161ebb1fe503ed9fb63585be72"
14789
15257
  };
14790
15258
 
14791
- // src/config/constants.ts
14792
- var USAGE_DAYS = 30;
14793
- var MODEL_NAME_COLUMN_WIDTH = 18;
14794
- var USAGE_CACHE_TTL = 5 * 60 * 1e3;
14795
-
14796
15259
  // src/agents/toolFilter.ts
14797
15260
  function matchesToolPattern2(toolName, pattern) {
14798
15261
  const regexPattern = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*");
@@ -16564,7 +17027,8 @@ function CliApp() {
16564
17027
  agentStore: null,
16565
17028
  abortController: null,
16566
17029
  contextContent: "",
16567
- backgroundManager: null
17030
+ backgroundManager: null,
17031
+ wsManager: null
16568
17032
  });
16569
17033
  const [isInitialized, setIsInitialized] = useState10(false);
16570
17034
  const [initError, setInitError] = useState10(null);
@@ -16592,6 +17056,10 @@ function CliApp() {
16592
17056
  })
16593
17057
  );
16594
17058
  }
17059
+ if (state.wsManager) {
17060
+ state.wsManager.disconnect();
17061
+ setWebSocketToolExecutor(null);
17062
+ }
16595
17063
  if (state.agent) {
16596
17064
  state.agent.removeAllListeners();
16597
17065
  }
@@ -16610,9 +17078,14 @@ function CliApp() {
16610
17078
  setTimeout(() => {
16611
17079
  process.exit(0);
16612
17080
  }, 100);
16613
- }, [state.session, state.sessionStore, state.mcpManager, state.agent, state.imageStore]);
17081
+ }, [state.session, state.sessionStore, state.mcpManager, state.agent, state.imageStore, state.wsManager]);
16614
17082
  useInput9((input, key) => {
16615
17083
  if (key.escape) {
17084
+ const store = useCliStore.getState();
17085
+ if (store.pastedContent) {
17086
+ store.clearPaste();
17087
+ return;
17088
+ }
16616
17089
  if (state.abortController) {
16617
17090
  logger.debug("[ABORT] ESC pressed - aborting current operation...");
16618
17091
  state.abortController.abort();
@@ -16630,9 +17103,9 @@ function CliApp() {
16630
17103
  });
16631
17104
  } else {
16632
17105
  logger.debug("[EXIT] First Ctrl+C - press again to exit");
16633
- const currentInput = useCliStore.getState().inputValue;
16634
- if (currentInput.length > 0) {
16635
- useCliStore.getState().clearInput();
17106
+ const store = useCliStore.getState();
17107
+ if (store.inputValue.length > 0 || store.pastedContent) {
17108
+ store.clearInput();
16636
17109
  }
16637
17110
  exitTimestamp = now;
16638
17111
  setExitRequested(true);
@@ -16680,7 +17153,7 @@ function CliApp() {
16680
17153
  if (!isAuthenticated) {
16681
17154
  console.log("\u2139\uFE0F AI features disabled. Available commands: /login, /help, /config\n");
16682
17155
  const minimalSession = {
16683
- id: uuidv411(),
17156
+ id: uuidv413(),
16684
17157
  name: `Session ${(/* @__PURE__ */ new Date()).toLocaleString()}`,
16685
17158
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
16686
17159
  updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
@@ -16708,10 +17181,45 @@ function CliApp() {
16708
17181
  console.log(`\u{1F30D} API Environment: ${envName} (${apiBaseURL})`);
16709
17182
  }
16710
17183
  const apiClient = new ApiClient(apiBaseURL, state.configStore);
16711
- const llm = new ServerLlmBackend({
16712
- apiClient,
16713
- model: config.defaultModel
16714
- });
17184
+ const tokenGetter = async () => {
17185
+ const tokens = await state.configStore.getAuthTokens();
17186
+ return tokens?.accessToken ?? null;
17187
+ };
17188
+ let wsManager = null;
17189
+ let llm;
17190
+ try {
17191
+ const serverConfig = await apiClient.get(
17192
+ "/api/settings/serverConfig"
17193
+ );
17194
+ const wsUrl = serverConfig?.websocketUrl;
17195
+ const wsCompletionUrl = serverConfig?.wsCompletionUrl;
17196
+ if (wsUrl && wsCompletionUrl) {
17197
+ wsManager = new WebSocketConnectionManager(wsUrl, tokenGetter);
17198
+ await wsManager.connect();
17199
+ const wsToolExecutor2 = new WebSocketToolExecutor(wsManager, tokenGetter);
17200
+ setWebSocketToolExecutor(wsToolExecutor2);
17201
+ llm = new WebSocketLlmBackend({
17202
+ wsManager,
17203
+ apiClient,
17204
+ model: config.defaultModel,
17205
+ tokenGetter,
17206
+ wsCompletionUrl
17207
+ });
17208
+ logger.debug("\u{1F50C} Using WebSocket transport (bypasses CloudFront timeout)");
17209
+ } else {
17210
+ throw new Error("No websocketUrl or wsCompletionUrl in server config");
17211
+ }
17212
+ } catch (wsError) {
17213
+ logger.debug(
17214
+ `[WS] WebSocket unavailable, using SSE fallback: ${wsError instanceof Error ? wsError.message : String(wsError)}`
17215
+ );
17216
+ wsManager = null;
17217
+ setWebSocketToolExecutor(null);
17218
+ llm = new ServerLlmBackend({
17219
+ apiClient,
17220
+ model: config.defaultModel
17221
+ });
17222
+ }
16715
17223
  const models = await llm.getModelInfo();
16716
17224
  if (models.length === 0) {
16717
17225
  throw new Error("No models available from server.");
@@ -16724,7 +17232,7 @@ function CliApp() {
16724
17232
  }
16725
17233
  llm.currentModel = modelInfo.id;
16726
17234
  const newSession = {
16727
- id: uuidv411(),
17235
+ id: uuidv413(),
16728
17236
  name: `Session ${(/* @__PURE__ */ new Date()).toLocaleString()}`,
16729
17237
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
16730
17238
  updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
@@ -16938,8 +17446,10 @@ function CliApp() {
16938
17446
  // Store agent store for agent management commands
16939
17447
  contextContent: contextResult.mergedContent,
16940
17448
  // Store raw context for compact instructions
16941
- backgroundManager
17449
+ backgroundManager,
16942
17450
  // Store for grouped notification turn tracking
17451
+ wsManager
17452
+ // WebSocket connection manager (null if using SSE fallback)
16943
17453
  }));
16944
17454
  setStoreSession(newSession);
16945
17455
  const bannerLines = [
@@ -17034,13 +17544,13 @@ function CliApp() {
17034
17544
  messageContent = multimodalMessage.content;
17035
17545
  }
17036
17546
  const userMessage = {
17037
- id: uuidv411(),
17547
+ id: uuidv413(),
17038
17548
  role: "user",
17039
17549
  content: userMessageContent,
17040
17550
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
17041
17551
  };
17042
17552
  const pendingAssistantMessage = {
17043
- id: uuidv411(),
17553
+ id: uuidv413(),
17044
17554
  role: "assistant",
17045
17555
  content: "...",
17046
17556
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
@@ -17253,13 +17763,13 @@ function CliApp() {
17253
17763
  userMessageContent = message;
17254
17764
  }
17255
17765
  const userMessage = {
17256
- id: uuidv411(),
17766
+ id: uuidv413(),
17257
17767
  role: "user",
17258
17768
  content: userMessageContent,
17259
17769
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
17260
17770
  };
17261
17771
  const pendingAssistantMessage = {
17262
- id: uuidv411(),
17772
+ id: uuidv413(),
17263
17773
  role: "assistant",
17264
17774
  content: "...",
17265
17775
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
@@ -17339,7 +17849,7 @@ function CliApp() {
17339
17849
  const currentSession = useCliStore.getState().session;
17340
17850
  if (currentSession) {
17341
17851
  const cancelMessage = {
17342
- id: uuidv411(),
17852
+ id: uuidv413(),
17343
17853
  role: "assistant",
17344
17854
  content: "\u26A0\uFE0F Operation cancelled by user",
17345
17855
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
@@ -17384,7 +17894,7 @@ function CliApp() {
17384
17894
  setState((prev) => ({ ...prev, abortController }));
17385
17895
  try {
17386
17896
  const pendingAssistantMessage = {
17387
- id: uuidv411(),
17897
+ id: uuidv413(),
17388
17898
  role: "assistant",
17389
17899
  content: "...",
17390
17900
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
@@ -17412,7 +17922,7 @@ function CliApp() {
17412
17922
  const currentSession = useCliStore.getState().session;
17413
17923
  if (!currentSession) return;
17414
17924
  const continuationMessage = {
17415
- id: uuidv411(),
17925
+ id: uuidv413(),
17416
17926
  role: "assistant",
17417
17927
  content: "---\n\n**Background Agent Results:**\n\n" + result.finalAnswer,
17418
17928
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
@@ -17473,13 +17983,13 @@ function CliApp() {
17473
17983
  isError = true;
17474
17984
  }
17475
17985
  const userMessage = {
17476
- id: uuidv411(),
17986
+ id: uuidv413(),
17477
17987
  role: "user",
17478
17988
  content: `$ ${command}`,
17479
17989
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
17480
17990
  };
17481
17991
  const assistantMessage = {
17482
- id: uuidv411(),
17992
+ id: uuidv413(),
17483
17993
  role: "assistant",
17484
17994
  content: isError ? `\u274C Error:
17485
17995
  ${output}` : output.trim() || "(no output)",
@@ -17948,7 +18458,7 @@ Keyboard Shortcuts:
17948
18458
  console.clear();
17949
18459
  const model = state.session?.model || state.config?.defaultModel || "claude-sonnet";
17950
18460
  const newSession = {
17951
- id: uuidv411(),
18461
+ id: uuidv413(),
17952
18462
  name: `Session ${(/* @__PURE__ */ new Date()).toLocaleString()}`,
17953
18463
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
17954
18464
  updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
@@ -18453,9 +18963,9 @@ No usage data available for the last ${USAGE_DAYS} days.`);
18453
18963
  return { ...prev, config: updatedConfig };
18454
18964
  });
18455
18965
  if (modelChanged && state.agent) {
18456
- const llm = state.agent.context.llm;
18457
- if (llm) {
18458
- llm.currentModel = updatedConfig.defaultModel;
18966
+ const backend = state.agent.context.llm;
18967
+ if (backend) {
18968
+ backend.currentModel = updatedConfig.defaultModel;
18459
18969
  }
18460
18970
  }
18461
18971
  };
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  CurationArtifactType
4
- } from "./chunk-C3K7SH2V.js";
4
+ } from "./chunk-55ZDRTGQ.js";
5
5
 
6
6
  // ../../b4m-core/packages/services/dist/src/notebookCurationService/llmMarkdownGenerator.js
7
7
  var DEFAULT_OPTIONS = {
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  CurationArtifactType
4
- } from "./chunk-C3K7SH2V.js";
4
+ } from "./chunk-55ZDRTGQ.js";
5
5
 
6
6
  // ../../b4m-core/packages/services/dist/src/notebookCurationService/markdownGenerator.js
7
7
  var DEFAULT_OPTIONS = {
@@ -2,9 +2,9 @@
2
2
  import {
3
3
  findMostSimilarMemento,
4
4
  getRelevantMementos
5
- } from "./chunk-65DLNQY2.js";
6
- import "./chunk-5HA4JEOV.js";
7
- import "./chunk-C3K7SH2V.js";
5
+ } from "./chunk-KBRR7H2K.js";
6
+ import "./chunk-7EEPLDUG.js";
7
+ import "./chunk-55ZDRTGQ.js";
8
8
  import "./chunk-OCYRD7D6.js";
9
9
  export {
10
10
  findMostSimilarMemento,
@@ -134,12 +134,12 @@ import {
134
134
  validateMermaidSyntax,
135
135
  warmUpSettingsCache,
136
136
  withRetry
137
- } from "./chunk-5HA4JEOV.js";
137
+ } from "./chunk-7EEPLDUG.js";
138
138
  import {
139
139
  buildRateLimitLogEntry,
140
140
  isNearLimit,
141
141
  parseRateLimitHeaders
142
- } from "./chunk-C3K7SH2V.js";
142
+ } from "./chunk-55ZDRTGQ.js";
143
143
  import {
144
144
  Logger,
145
145
  NotificationDeduplicator,
@@ -41,6 +41,12 @@ import {
41
41
  ChatModels,
42
42
  CitableSourceSchema,
43
43
  ClaudeArtifactMimeTypes,
44
+ CliCompletionChunkAction,
45
+ CliCompletionDoneAction,
46
+ CliCompletionErrorAction,
47
+ CliCompletionRequestAction,
48
+ CliToolRequestAction,
49
+ CliToolResponseAction,
44
50
  CollectionType,
45
51
  CompletionApiUsageTransaction,
46
52
  CompletionRequestSchema,
@@ -419,7 +425,7 @@ import {
419
425
  validateReactArtifactV2,
420
426
  validateSvgArtifactV2,
421
427
  wikiMarkupToAdf
422
- } from "./chunk-C3K7SH2V.js";
428
+ } from "./chunk-55ZDRTGQ.js";
423
429
  export {
424
430
  ALL_IMAGE_MODELS,
425
431
  ALL_IMAGE_SIZES,
@@ -463,6 +469,12 @@ export {
463
469
  ChatModels,
464
470
  CitableSourceSchema,
465
471
  ClaudeArtifactMimeTypes,
472
+ CliCompletionChunkAction,
473
+ CliCompletionDoneAction,
474
+ CliCompletionErrorAction,
475
+ CliCompletionRequestAction,
476
+ CliToolRequestAction,
477
+ CliToolResponseAction,
466
478
  CollectionType,
467
479
  CompletionApiUsageTransaction,
468
480
  CompletionRequestSchema,
@@ -3,7 +3,7 @@ import {
3
3
  selectActiveBackgroundAgents,
4
4
  selectCompletedBackgroundAgents,
5
5
  useCliStore
6
- } from "./chunk-TVW4ZESU.js";
6
+ } from "./chunk-BYXFQJYT.js";
7
7
  export {
8
8
  selectActiveBackgroundAgents,
9
9
  selectCompletedBackgroundAgents,
@@ -2,9 +2,9 @@
2
2
  import {
3
3
  SubtractCreditsSchema,
4
4
  subtractCredits
5
- } from "./chunk-BYLWQZ5S.js";
6
- import "./chunk-5HA4JEOV.js";
7
- import "./chunk-C3K7SH2V.js";
5
+ } from "./chunk-XXYKIX5X.js";
6
+ import "./chunk-7EEPLDUG.js";
7
+ import "./chunk-55ZDRTGQ.js";
8
8
  import "./chunk-OCYRD7D6.js";
9
9
  export {
10
10
  SubtractCreditsSchema,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bike4mind/cli",
3
- "version": "0.2.30-feat-cli-scrollable-command-autocomplete.19204+bf1e097e7",
3
+ "version": "0.2.30-feat-cli-websocket-streaming.19243+6a95a9d10",
4
4
  "type": "module",
5
5
  "description": "Interactive CLI tool for Bike4Mind with ReAct agents",
6
6
  "license": "UNLICENSED",
@@ -111,10 +111,10 @@
111
111
  },
112
112
  "devDependencies": {
113
113
  "@bike4mind/agents": "0.1.0",
114
- "@bike4mind/common": "2.51.1-feat-cli-scrollable-command-autocomplete.19204+bf1e097e7",
115
- "@bike4mind/mcp": "1.30.1-feat-cli-scrollable-command-autocomplete.19204+bf1e097e7",
116
- "@bike4mind/services": "2.49.1-feat-cli-scrollable-command-autocomplete.19204+bf1e097e7",
117
- "@bike4mind/utils": "2.6.1-feat-cli-scrollable-command-autocomplete.19204+bf1e097e7",
114
+ "@bike4mind/common": "2.51.1-feat-cli-websocket-streaming.19243+6a95a9d10",
115
+ "@bike4mind/mcp": "1.30.1-feat-cli-websocket-streaming.19243+6a95a9d10",
116
+ "@bike4mind/services": "2.49.1-feat-cli-websocket-streaming.19243+6a95a9d10",
117
+ "@bike4mind/utils": "2.6.1-feat-cli-websocket-streaming.19243+6a95a9d10",
118
118
  "@types/better-sqlite3": "^7.6.13",
119
119
  "@types/diff": "^5.0.9",
120
120
  "@types/jsonwebtoken": "^9.0.4",
@@ -135,5 +135,5 @@
135
135
  "optionalDependencies": {
136
136
  "@vscode/ripgrep": "^1.17.0"
137
137
  },
138
- "gitHead": "bf1e097e7c47caaaa19b04b39a11fd1e509efab2"
138
+ "gitHead": "6a95a9d108b3eb161ebb1fe503ed9fb63585be72"
139
139
  }