blun-king-cli 9.1.25 → 9.1.26

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.
Files changed (2) hide show
  1. package/blun.mjs +830 -45
  2. package/package.json +1 -1
package/blun.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- // BLUN_BUILD_INPUT_SHA256:390518244a4c2f3bd6dba9ae2b00963abb0416572bc276f8c933f99a17ae0d33
2
+ // BLUN_BUILD_INPUT_SHA256:7d2032417fd49eb3d28a7006057755559c5bb8da252a52c972b2ccc12a33dd14
3
3
  import { fileURLToPath as __cjsShimFileURLToPath } from 'node:url';
4
4
  import { dirname as __cjsShimDirname } from 'node:path';
5
5
  const __filename = __cjsShimFileURLToPath(import.meta.url);
@@ -76385,7 +76385,7 @@ var init_telemetry_events = __esmMin((() => {
76385
76385
  //#endregion
76386
76386
  //#region ../../packages/agent-core/src/agent/cron/manager.ts
76387
76387
  var STALE_THRESHOLD_MS, CronManager;
76388
- var init_manager$2 = __esmMin((() => {
76388
+ var init_manager$3 = __esmMin((() => {
76389
76389
  init_clock();
76390
76390
  init_cron_fire_xml();
76391
76391
  init_persist();
@@ -76797,7 +76797,7 @@ var init_manager$2 = __esmMin((() => {
76797
76797
  //#endregion
76798
76798
  //#region ../../packages/agent-core/src/agent/cron/index.ts
76799
76799
  var init_cron$1 = __esmMin((() => {
76800
- init_manager$2();
76800
+ init_manager$3();
76801
76801
  }));
76802
76802
  //#endregion
76803
76803
  //#region ../../packages/agent-core/src/tools/policies/sensitive.ts
@@ -232055,7 +232055,7 @@ var init_mission_contract_bridge = __esmMin((() => {
232055
232055
  //#endregion
232056
232056
  //#region ../../packages/agent-core/src/agent/injection/manager.ts
232057
232057
  var ACTIVE_BACKGROUND_TASK_GUIDANCE, InjectionManager;
232058
- var init_manager$1 = __esmMin((() => {
232058
+ var init_manager$2 = __esmMin((() => {
232059
232059
  init_task_list();
232060
232060
  init_action_style();
232061
232061
  init_error_memory$1();
@@ -252200,6 +252200,26 @@ var init_edit$1 = __esmMin((() => {
252200
252200
  edit_default = "Perform exact replacements in existing files.\n\n- Edit is mandatory for every incremental change, especially small edits. DO NOT use Write or Bash `sed`.\n- Read the target file before every Edit. DO NOT call Edit from memory, stale context, or a guessed `old_string`.\n- Take `old_string` and `new_string` from the Read output view.\n- Drop the line-number prefix and tab; match only file content.\n- `old_string` must be unique unless `replace_all` is set.\n- If `old_string` is ambiguous, add surrounding context. Use `replace_all` only when every occurrence should change — for example, renaming a symbol throughout the file.\n- Multiple Edit calls may run in one response only when they do not target the same file.\n- DO NOT issue consecutive Edit calls on the same file. A previous Edit can invalidate a later Edit's `old_string`, causing `old_string not found`. Read the file again before the next Edit.\n- A write lock serializes same-file edits in response order, but serialization does not make stale `old_string` valid.\n- For pure CRLF files, Read shows LF; use LF in `old_string` and `new_string`, and Edit writes CRLF back.\n- For mixed endings or lone carriage returns, Read shows carriage returns as \\r; include actual \\r escapes in those positions.\n- Source files may contain at most 500 lines. An already oversized source may only be edited when the result has fewer lines.\n- Set `single_file_override=true` only when the latest direct user message explicitly requires one single file. The accepted override is reported visibly.\n";
252201
252201
  }));
252202
252202
  //#endregion
252203
+ //#region ../../packages/agent-core/src/tools/builtin/file/lsp-diagnostics.ts
252204
+ async function appendLspDiagnostics(result, path, lsp) {
252205
+ if (lsp === void 0 || result.isError === true || typeof result.output !== "string") return result;
252206
+ try {
252207
+ const diagnostics = await lsp.diagnosticsAfterChange(path);
252208
+ if (diagnostics === void 0 || diagnostics.length === 0) return result;
252209
+ return {
252210
+ ...result,
252211
+ output: `${result.output}\nLSP diagnostics:\n${JSON.stringify(diagnostics, null, 2)}`
252212
+ };
252213
+ } catch (error) {
252214
+ const message = error instanceof Error ? error.message : String(error);
252215
+ return {
252216
+ ...result,
252217
+ output: `${result.output}\nLSP diagnostics unavailable: ${message}`
252218
+ };
252219
+ }
252220
+ }
252221
+ var init_lsp_diagnostics = __esmMin((() => {}));
252222
+ //#endregion
252203
252223
  //#region ../../packages/agent-core/src/tools/builtin/file/edit.ts
252204
252224
  function replaceOnceLiteral(content, oldString, newString) {
252205
252225
  const index = content.indexOf(oldString);
@@ -252216,6 +252236,7 @@ var init_edit = __esmMin((() => {
252216
252236
  init_line_endings();
252217
252237
  init_source_file_line_limit();
252218
252238
  init_edit$1();
252239
+ init_lsp_diagnostics();
252219
252240
  EditInputSchema = object({
252220
252241
  path: string().describe("Path to the text file to edit. Relative paths resolve against the working directory; a path outside the working directory must be absolute."),
252221
252242
  old_string: string().min(1).describe("Exact content to replace from the Read output view, without the line-number prefix. Use LF for pure CRLF files; use actual \\r escapes where Read shows \\r."),
@@ -252227,13 +252248,15 @@ var init_edit = __esmMin((() => {
252227
252248
  kaos;
252228
252249
  workspace;
252229
252250
  history;
252251
+ lsp;
252230
252252
  name = "Edit";
252231
252253
  description = edit_default;
252232
252254
  parameters = toInputJsonSchema(EditInputSchema);
252233
- constructor(kaos, workspace, history) {
252255
+ constructor(kaos, workspace, history, lsp) {
252234
252256
  this.kaos = kaos;
252235
252257
  this.workspace = workspace;
252236
252258
  this.history = history;
252259
+ this.lsp = lsp;
252237
252260
  }
252238
252261
  resolveExecution(args) {
252239
252262
  const path = resolvePathAccessPath(args.path, {
@@ -252324,7 +252347,7 @@ var init_edit = __esmMin((() => {
252324
252347
  await this.kaos.writeText(safePath, materialized);
252325
252348
  const occurrence = replacementCount === 1 ? "occurrence" : "occurrences";
252326
252349
  const notice = lineLimit.notice === void 0 ? "" : ` ${lineLimit.notice}`;
252327
- return { output: `Replaced ${String(replacementCount)} ${occurrence} in ${args.path}.` + notice };
252350
+ return appendLspDiagnostics({ output: `Replaced ${String(replacementCount)} ${occurrence} in ${args.path}.` + notice }, safePath, this.lsp);
252328
252351
  }
252329
252352
  };
252330
252353
  }));
@@ -258411,6 +258434,7 @@ var init_write = __esmMin((() => {
258411
258434
  init_rule_match();
258412
258435
  init_source_file_line_limit();
258413
258436
  init_write$1();
258437
+ init_lsp_diagnostics();
258414
258438
  S_IFMT = 61440;
258415
258439
  S_IFDIR = 16384;
258416
258440
  WriteInputSchema = object({
@@ -258426,13 +258450,15 @@ bytesWritten: number$1().int().nonnegative() });
258426
258450
  kaos;
258427
258451
  workspace;
258428
258452
  history;
258453
+ lsp;
258429
258454
  name = "Write";
258430
258455
  description = write_default;
258431
258456
  parameters = toInputJsonSchema(WriteInputSchema);
258432
- constructor(kaos, workspace, history) {
258457
+ constructor(kaos, workspace, history, lsp) {
258433
258458
  this.kaos = kaos;
258434
258459
  this.workspace = workspace;
258435
258460
  this.history = history;
258461
+ this.lsp = lsp;
258436
258462
  }
258437
258463
  resolveExecution(args) {
258438
258464
  const path = resolvePathAccessPath(args.path, {
@@ -258489,7 +258515,7 @@ bytesWritten: number$1().int().nonnegative() });
258489
258515
  else await this.kaos.writeText(safePath, args.content);
258490
258516
  const bytesWritten = Buffer.byteLength(args.content, "utf8");
258491
258517
  const notice = lineLimit.notice === void 0 ? "" : ` ${lineLimit.notice}`;
258492
- return { output: `${mode === "append" ? "Appended" : "Wrote"} ${String(bytesWritten)} bytes to ${args.path}.${notice}` };
258518
+ return appendLspDiagnostics({ output: `${mode === "append" ? "Appended" : "Wrote"} ${String(bytesWritten)} bytes to ${args.path}.${notice}` }, safePath, this.lsp);
258493
258519
  } catch (error) {
258494
258520
  if (error?.code === "ENOENT") return {
258495
258521
  isError: true,
@@ -261017,6 +261043,83 @@ var init_codebase_search = __esmMin((() => {
261017
261043
  };
261018
261044
  }));
261019
261045
  //#endregion
261046
+ //#region ../../packages/agent-core/src/tools/builtin/search/lsp.md?raw
261047
+ var lsp_default;
261048
+ var init_lsp$2 = __esmMin((() => {
261049
+ lsp_default = "Query a configured Language Server Protocol (LSP) server for precise code intelligence.\n\nUse this tool for definitions, references, hover/type information, document or workspace symbols, implementations, and current diagnostics. Line and character positions are one-based. Use `workspace_symbols` with `query`; all other operations require `path`. Position-based operations also require `line` and `character`.\n\nLanguage servers are provided by enabled plugins and start only when first used. If the required server executable is missing, report that exact setup error instead of falling back to a guess.\n";
261050
+ }));
261051
+ //#endregion
261052
+ //#region ../../packages/agent-core/src/tools/builtin/search/lsp.ts
261053
+ var LspInputSchema, LspTool;
261054
+ var init_lsp$1 = __esmMin((() => {
261055
+ init_zod$1();
261056
+ init_tool_access();
261057
+ init_path_access();
261058
+ init_input_schema();
261059
+ init_rule_match();
261060
+ init_lsp$2();
261061
+ LspInputSchema = object({
261062
+ operation: _enum([
261063
+ "definition",
261064
+ "references",
261065
+ "hover",
261066
+ "document_symbols",
261067
+ "workspace_symbols",
261068
+ "implementation",
261069
+ "diagnostics"
261070
+ ]),
261071
+ path: string().optional().describe("Source file path, relative to the working directory or absolute."),
261072
+ line: number$1().int().positive().optional().describe("One-based source line."),
261073
+ character: number$1().int().positive().optional().describe("One-based character offset."),
261074
+ query: string().optional().describe("Symbol query for workspace_symbols.")
261075
+ });
261076
+ LspTool = class {
261077
+ service;
261078
+ kaos;
261079
+ workspace;
261080
+ name = "LSP";
261081
+ description = lsp_default;
261082
+ parameters = toInputJsonSchema(LspInputSchema);
261083
+ constructor(service, kaos, workspace) {
261084
+ this.service = service;
261085
+ this.kaos = kaos;
261086
+ this.workspace = workspace;
261087
+ }
261088
+ resolveExecution(args) {
261089
+ const safePath = args.path === void 0 || this.kaos === void 0 || this.workspace === void 0 ? args.path : resolvePathAccessPath(args.path, {
261090
+ kaos: this.kaos,
261091
+ workspace: this.workspace,
261092
+ operation: "read"
261093
+ });
261094
+ return {
261095
+ description: `LSP ${args.operation}${args.path === void 0 ? "" : `: ${args.path}`}`,
261096
+ accesses: safePath === void 0 ? ToolAccesses.searchTree(this.workspace?.workspaceDir ?? ".") : ToolAccesses.readFile(safePath),
261097
+ approvalRule: literalRulePattern(this.name, args.operation),
261098
+ matchesRule: safePath === void 0 || this.kaos === void 0 || this.workspace === void 0 ? void 0 : (ruleArgs) => matchesPathRuleSubject(ruleArgs, safePath, {
261099
+ cwd: this.workspace.workspaceDir,
261100
+ pathClass: this.kaos.pathClass(),
261101
+ homeDir: this.kaos.gethome()
261102
+ }),
261103
+ execute: () => this.execute({
261104
+ ...args,
261105
+ path: safePath
261106
+ })
261107
+ };
261108
+ }
261109
+ async execute(args) {
261110
+ try {
261111
+ const result = await this.service.request(args);
261112
+ return { output: JSON.stringify(result ?? null, null, 2) };
261113
+ } catch (error) {
261114
+ return {
261115
+ isError: true,
261116
+ output: error instanceof Error ? error.message : String(error)
261117
+ };
261118
+ }
261119
+ }
261120
+ };
261121
+ }));
261122
+ //#endregion
261020
261123
  //#region ../../packages/agent-core/src/tools/builtin/mistake-record.md?raw
261021
261124
  var mistake_record_default;
261022
261125
  var init_mistake_record$1 = __esmMin((() => {
@@ -261391,6 +261494,7 @@ var init_builtin = __esmMin((() => {
261391
261494
  init_fetch_url();
261392
261495
  init_web_search();
261393
261496
  init_codebase_search();
261497
+ init_lsp$1();
261394
261498
  init_mistake_record();
261395
261499
  init_blun_media$1();
261396
261500
  }));
@@ -261789,8 +261893,8 @@ var init_tool$1 = __esmMin((() => {
261789
261893
  const goalToolsEnabled = this.agent.type === "main";
261790
261894
  this.builtinTools = new Map([
261791
261895
  new ReadTool(kaos, workspace),
261792
- new WriteTool(kaos, workspace, () => this.agent.context.history),
261793
- new EditTool(kaos, workspace, () => this.agent.context.history),
261896
+ new WriteTool(kaos, workspace, () => this.agent.context.history, toolServices?.lsp),
261897
+ new EditTool(kaos, workspace, () => this.agent.context.history, toolServices?.lsp),
261794
261898
  new GrepTool(kaos, workspace, this.agent.telemetry),
261795
261899
  new GlobTool(kaos, workspace, this.agent.telemetry),
261796
261900
  new BashTool(kaos, cwd, background, { allowBackground }),
@@ -261826,7 +261930,8 @@ var init_tool$1 = __esmMin((() => {
261826
261930
  toolServices?.media && new LipSyncMediaTool(toolServices.media),
261827
261931
  toolServices?.media && new GetMediaTool(toolServices.media),
261828
261932
  new MistakeRecordTool(this.agent),
261829
- new CodebaseSearchTool(cwd)
261933
+ new CodebaseSearchTool(cwd),
261934
+ toolServices?.lsp && new LspTool(toolServices.lsp, kaos, workspace)
261830
261935
  ].filter((tool) => !!tool).map((tool) => [tool.name, tool]));
261831
261936
  }
261832
261937
  refreshBuiltinTools() {
@@ -262220,7 +262325,7 @@ var init_agent = __esmMin((() => {
262220
262325
  init_error_memory();
262221
262326
  init_goal$1();
262222
262327
  init_hooks();
262223
- init_manager$1();
262328
+ init_manager$2();
262224
262329
  init_permission();
262225
262330
  init_plan();
262226
262331
  init_records();
@@ -263955,7 +264060,7 @@ var init_ajv_provider = __esmMin((() => {
263955
264060
  //#endregion
263956
264061
  //#region ../../node_modules/.pnpm/@modelcontextprotocol+sdk@1.29.0_zod@4.3.6/node_modules/@modelcontextprotocol/sdk/dist/esm/experimental/tasks/client.js
263957
264062
  var ExperimentalClientTasks;
263958
- var init_client$2 = __esmMin((() => {
264063
+ var init_client$3 = __esmMin((() => {
263959
264064
  init_types$4();
263960
264065
  ExperimentalClientTasks = class {
263961
264066
  constructor(_client) {
@@ -264213,12 +264318,12 @@ function getSupportedElicitationModes(capabilities) {
264213
264318
  };
264214
264319
  }
264215
264320
  var Client;
264216
- var init_client$1 = __esmMin((() => {
264321
+ var init_client$2 = __esmMin((() => {
264217
264322
  init_protocol();
264218
264323
  init_types$4();
264219
264324
  init_ajv_provider();
264220
264325
  init_zod_compat();
264221
- init_client$2();
264326
+ init_client$3();
264222
264327
  init_helpers$1();
264223
264328
  Client = class extends Protocol {
264224
264329
  /**
@@ -265237,7 +265342,7 @@ function buildMcpHttpHeaders(config, envLookup) {
265237
265342
  }
265238
265343
  var HttpMcpClient;
265239
265344
  var init_client_http = __esmMin((() => {
265240
- init_client$1();
265345
+ init_client$2();
265241
265346
  init_streamableHttp();
265242
265347
  init_client_shared();
265243
265348
  init_client_remote();
@@ -265759,7 +265864,7 @@ function isTerminalSseTransportError(error) {
265759
265864
  }
265760
265865
  var SseMcpClient;
265761
265866
  var init_client_sse = __esmMin((() => {
265762
- init_client$1();
265867
+ init_client$2();
265763
265868
  init_sse();
265764
265869
  init_client_shared();
265765
265870
  init_client_remote();
@@ -292969,7 +293074,7 @@ var STDERR_BUFFER_CAPACITY, StdioMcpClient, BoundedTail;
292969
293074
  var init_client_stdio = __esmMin((() => {
292970
293075
  init_errors$8();
292971
293076
  init_proxy();
292972
- init_client$1();
293077
+ init_client$2();
292973
293078
  init_stdio();
292974
293079
  init_dist$6();
292975
293080
  init_client_shared();
@@ -293235,7 +293340,7 @@ async function withTimeout(promise, timeoutMs, onTimeout) {
293235
293340
  if (timer !== void 0) clearTimeout(timer);
293236
293341
  }
293237
293342
  }
293238
- var DEFAULT_STARTUP_TIMEOUT_MS, McpConnectionManager;
293343
+ var DEFAULT_STARTUP_TIMEOUT_MS$1, McpConnectionManager;
293239
293344
  var init_connection_manager = __esmMin((() => {
293240
293345
  init_errors$8();
293241
293346
  init_logger$1();
@@ -293246,7 +293351,7 @@ var init_connection_manager = __esmMin((() => {
293246
293351
  init_client_stdio();
293247
293352
  init_public_display();
293248
293353
  init_types$1();
293249
- DEFAULT_STARTUP_TIMEOUT_MS = 3e4;
293354
+ DEFAULT_STARTUP_TIMEOUT_MS$1 = 3e4;
293250
293355
  McpConnectionManager = class {
293251
293356
  options;
293252
293357
  entries = /* @__PURE__ */ new Map();
@@ -293403,7 +293508,7 @@ var init_connection_manager = __esmMin((() => {
293403
293508
  await Promise.allSettled(tasks);
293404
293509
  }
293405
293510
  async connectOne(entry, attemptId) {
293406
- const timeoutMs = entry.config.startupTimeoutMs ?? DEFAULT_STARTUP_TIMEOUT_MS;
293511
+ const timeoutMs = entry.config.startupTimeoutMs ?? DEFAULT_STARTUP_TIMEOUT_MS$1;
293407
293512
  let client;
293408
293513
  try {
293409
293514
  const startupClient = this.createClient(entry.config, entry.name);
@@ -293745,6 +293850,446 @@ var init_legacy_memory = __esmMin((() => {
293745
293850
  LEGACY_LOCAL_MEMORY_SAVE = "When the user tells you something worth remembering (preferences, facts about them, project context, corrections), SAVE it:";
293746
293851
  }));
293747
293852
  //#endregion
293853
+ //#region ../../packages/agent-core/src/lsp/client.ts
293854
+ async function waitForExit$1(child, timeoutMs) {
293855
+ if (child.exitCode !== null) return;
293856
+ await Promise.race([new Promise((resolve) => {
293857
+ child.once("exit", () => resolve());
293858
+ }), delay(timeoutMs)]);
293859
+ }
293860
+ function delay(timeoutMs) {
293861
+ return new Promise((resolve) => {
293862
+ setTimeout(resolve, timeoutMs).unref?.();
293863
+ });
293864
+ }
293865
+ var DEFAULT_STARTUP_TIMEOUT_MS, DEFAULT_SHUTDOWN_TIMEOUT_MS, REQUEST_TIMEOUT_MS, StdioLspClient;
293866
+ var init_client$1 = __esmMin((() => {
293867
+ DEFAULT_STARTUP_TIMEOUT_MS = 1e4;
293868
+ DEFAULT_SHUTDOWN_TIMEOUT_MS = 2e3;
293869
+ REQUEST_TIMEOUT_MS = 3e4;
293870
+ StdioLspClient = class {
293871
+ name;
293872
+ config;
293873
+ cwd;
293874
+ process;
293875
+ startPromise;
293876
+ stdout = Buffer.alloc(0);
293877
+ nextId = 1;
293878
+ stopped = false;
293879
+ _generation = 0;
293880
+ pending = /* @__PURE__ */ new Map();
293881
+ diagnosticsByUri = /* @__PURE__ */ new Map();
293882
+ diagnosticsWaiters = /* @__PURE__ */ new Map();
293883
+ constructor(name, config, cwd) {
293884
+ this.name = name;
293885
+ this.config = config;
293886
+ this.cwd = cwd;
293887
+ }
293888
+ get running() {
293889
+ return this.process !== void 0 && this.process.exitCode === null && !this.stopped;
293890
+ }
293891
+ get generation() {
293892
+ return this._generation;
293893
+ }
293894
+ async start() {
293895
+ if (this.running) return;
293896
+ if (this.startPromise !== void 0) return this.startPromise;
293897
+ this.startPromise = this.startInternal().catch((error) => {
293898
+ this.startPromise = void 0;
293899
+ throw error;
293900
+ });
293901
+ return this.startPromise;
293902
+ }
293903
+ async request(method, params) {
293904
+ await this.start();
293905
+ return this.sendRequest(method, params, REQUEST_TIMEOUT_MS);
293906
+ }
293907
+ notify(method, params) {
293908
+ if (!this.running) throw new Error(`LSP server "${this.name}" is not running`);
293909
+ this.write({
293910
+ jsonrpc: "2.0",
293911
+ method,
293912
+ params
293913
+ });
293914
+ }
293915
+ diagnostics(uri) {
293916
+ return this.diagnosticsByUri.get(uri);
293917
+ }
293918
+ clearDiagnostics(uri) {
293919
+ this.diagnosticsByUri.delete(uri);
293920
+ }
293921
+ async waitForDiagnostics(uri, timeoutMs = 300) {
293922
+ const current = this.diagnosticsByUri.get(uri);
293923
+ if (current !== void 0) return current;
293924
+ let notify;
293925
+ const notification = new Promise((resolve) => {
293926
+ notify = resolve;
293927
+ const waiters = this.diagnosticsWaiters.get(uri) ?? [];
293928
+ waiters.push(resolve);
293929
+ this.diagnosticsWaiters.set(uri, waiters);
293930
+ });
293931
+ await Promise.race([notification, delay(timeoutMs)]);
293932
+ const waiters = this.diagnosticsWaiters.get(uri);
293933
+ if (waiters !== void 0) {
293934
+ const remaining = waiters.filter((waiter) => waiter !== notify);
293935
+ if (remaining.length === 0) this.diagnosticsWaiters.delete(uri);
293936
+ else this.diagnosticsWaiters.set(uri, remaining);
293937
+ }
293938
+ return this.diagnosticsByUri.get(uri) ?? [];
293939
+ }
293940
+ async shutdown() {
293941
+ this.stopped = true;
293942
+ const child = this.process;
293943
+ if (child === void 0 || child.exitCode !== null) return;
293944
+ try {
293945
+ await this.sendRequest("shutdown", null, this.config.shutdownTimeoutMs ?? DEFAULT_SHUTDOWN_TIMEOUT_MS);
293946
+ this.write({
293947
+ jsonrpc: "2.0",
293948
+ method: "exit",
293949
+ params: null
293950
+ });
293951
+ } catch {}
293952
+ await waitForExit$1(child, this.config.shutdownTimeoutMs ?? DEFAULT_SHUTDOWN_TIMEOUT_MS);
293953
+ if (child.exitCode === null) child.kill();
293954
+ this.process = void 0;
293955
+ this.startPromise = void 0;
293956
+ }
293957
+ async startInternal() {
293958
+ this.stopped = false;
293959
+ const child = spawn(this.config.command, [...this.config.args ?? []], {
293960
+ cwd: this.config.workspaceFolder ?? this.cwd,
293961
+ env: {
293962
+ ...process.env,
293963
+ ...this.config.env
293964
+ },
293965
+ stdio: [
293966
+ "pipe",
293967
+ "pipe",
293968
+ "pipe"
293969
+ ],
293970
+ windowsHide: true
293971
+ });
293972
+ this.process = child;
293973
+ child.stdout.on("data", (chunk) => this.onStdout(chunk));
293974
+ child.stderr.on("data", () => {});
293975
+ child.once("error", (error) => this.failProcess(error));
293976
+ child.once("exit", (code, signal) => {
293977
+ if (!this.stopped) this.failProcess(/* @__PURE__ */ new Error(`LSP server "${this.name}" exited unexpectedly (${code === null ? signal : String(code)})`));
293978
+ });
293979
+ const rootPath = this.config.workspaceFolder ?? this.cwd;
293980
+ const rootUri = pathToFileURL(rootPath).href;
293981
+ try {
293982
+ await this.sendRequest("initialize", {
293983
+ processId: process.pid,
293984
+ clientInfo: {
293985
+ name: "BLUN Code",
293986
+ version: "1"
293987
+ },
293988
+ rootPath,
293989
+ rootUri,
293990
+ workspaceFolders: [{
293991
+ uri: rootUri,
293992
+ name: this.name
293993
+ }],
293994
+ capabilities: {
293995
+ textDocument: {
293996
+ hover: { contentFormat: ["markdown", "plaintext"] },
293997
+ definition: { linkSupport: true },
293998
+ implementation: { linkSupport: true },
293999
+ publishDiagnostics: { relatedInformation: true }
294000
+ },
294001
+ workspace: { symbol: { resolveSupport: { properties: ["location.range"] } } }
294002
+ },
294003
+ initializationOptions: this.config.initializationOptions
294004
+ }, this.config.startupTimeoutMs ?? DEFAULT_STARTUP_TIMEOUT_MS);
294005
+ this.notify("initialized", {});
294006
+ this._generation++;
294007
+ if (this.config.settings !== void 0) this.notify("workspace/didChangeConfiguration", { settings: this.config.settings });
294008
+ } catch (error) {
294009
+ if (child.exitCode === null) child.kill();
294010
+ const message = error instanceof Error ? error.message : String(error);
294011
+ throw new Error(`Failed to start LSP server "${this.name}" (${this.config.command}): ${message}`, { cause: error });
294012
+ }
294013
+ }
294014
+ sendRequest(method, params, timeoutMs) {
294015
+ const id = this.nextId++;
294016
+ return new Promise((resolve, reject) => {
294017
+ const timer = setTimeout(() => {
294018
+ this.pending.delete(id);
294019
+ reject(/* @__PURE__ */ new Error(`LSP request "${method}" timed out after ${String(timeoutMs)} ms`));
294020
+ }, timeoutMs);
294021
+ timer.unref?.();
294022
+ this.pending.set(id, {
294023
+ resolve,
294024
+ reject,
294025
+ timer
294026
+ });
294027
+ try {
294028
+ this.write({
294029
+ jsonrpc: "2.0",
294030
+ id,
294031
+ method,
294032
+ params
294033
+ });
294034
+ } catch (error) {
294035
+ clearTimeout(timer);
294036
+ this.pending.delete(id);
294037
+ reject(error instanceof Error ? error : new Error(String(error)));
294038
+ }
294039
+ });
294040
+ }
294041
+ write(message) {
294042
+ const stdin = this.process?.stdin;
294043
+ if (stdin === void 0 || stdin.destroyed) throw new Error(`LSP server "${this.name}" is not available`);
294044
+ const body = Buffer.from(JSON.stringify(message), "utf8");
294045
+ stdin.write(`Content-Length: ${String(body.length)}\r\n\r\n`);
294046
+ stdin.write(body);
294047
+ }
294048
+ onStdout(chunk) {
294049
+ this.stdout = Buffer.concat([this.stdout, chunk]);
294050
+ while (true) {
294051
+ const headerEnd = this.stdout.indexOf("\r\n\r\n");
294052
+ if (headerEnd < 0) return;
294053
+ const header = this.stdout.subarray(0, headerEnd).toString("ascii");
294054
+ const match = /(?:^|\r\n)Content-Length:\s*(\d+)/i.exec(header);
294055
+ if (match === null) {
294056
+ this.failProcess(/* @__PURE__ */ new Error(`Invalid LSP response header from "${this.name}"`));
294057
+ return;
294058
+ }
294059
+ const length = Number(match[1]);
294060
+ const bodyStart = headerEnd + 4;
294061
+ if (this.stdout.length < bodyStart + length) return;
294062
+ const body = this.stdout.subarray(bodyStart, bodyStart + length).toString("utf8");
294063
+ this.stdout = this.stdout.subarray(bodyStart + length);
294064
+ try {
294065
+ this.handleMessage(JSON.parse(body));
294066
+ } catch (error) {
294067
+ this.failProcess(error instanceof Error ? error : new Error(String(error)));
294068
+ return;
294069
+ }
294070
+ }
294071
+ }
294072
+ handleMessage(message) {
294073
+ if (message.id !== void 0 && message.method !== void 0) {
294074
+ this.handleServerRequest(message.id, message.method, message.params);
294075
+ return;
294076
+ }
294077
+ if (message.id !== void 0) {
294078
+ const pending = this.pending.get(message.id);
294079
+ if (pending === void 0) return;
294080
+ clearTimeout(pending.timer);
294081
+ this.pending.delete(message.id);
294082
+ if (message.error !== void 0) pending.reject(/* @__PURE__ */ new Error(`LSP request failed${message.error.code === void 0 ? "" : ` (${String(message.error.code)})`}: ${message.error.message ?? "unknown error"}`));
294083
+ else pending.resolve(message.result);
294084
+ return;
294085
+ }
294086
+ if (message.method !== "textDocument/publishDiagnostics") return;
294087
+ const params = message.params;
294088
+ if (typeof params?.uri !== "string" || !Array.isArray(params.diagnostics)) return;
294089
+ this.diagnosticsByUri.set(params.uri, params.diagnostics);
294090
+ const waiters = this.diagnosticsWaiters.get(params.uri) ?? [];
294091
+ this.diagnosticsWaiters.delete(params.uri);
294092
+ for (const resolve of waiters) resolve();
294093
+ }
294094
+ handleServerRequest(id, method, params) {
294095
+ if (method === "workspace/configuration") {
294096
+ const items = params?.items;
294097
+ const count = Array.isArray(items) ? items.length : 0;
294098
+ this.write({
294099
+ jsonrpc: "2.0",
294100
+ id,
294101
+ result: Array.from({ length: count }, () => this.config.settings ?? null)
294102
+ });
294103
+ return;
294104
+ }
294105
+ if (method === "workspace/workspaceFolders") {
294106
+ const rootPath = this.config.workspaceFolder ?? this.cwd;
294107
+ this.write({
294108
+ jsonrpc: "2.0",
294109
+ id,
294110
+ result: [{
294111
+ uri: pathToFileURL(rootPath).href,
294112
+ name: this.name
294113
+ }]
294114
+ });
294115
+ return;
294116
+ }
294117
+ if (method === "client/registerCapability" || method === "client/unregisterCapability" || method === "window/workDoneProgress/create") {
294118
+ this.write({
294119
+ jsonrpc: "2.0",
294120
+ id,
294121
+ result: null
294122
+ });
294123
+ return;
294124
+ }
294125
+ this.write({
294126
+ jsonrpc: "2.0",
294127
+ id,
294128
+ error: {
294129
+ code: -32601,
294130
+ message: `Client method not supported: ${method}`
294131
+ }
294132
+ });
294133
+ }
294134
+ failProcess(error) {
294135
+ const wrapped = new Error(`LSP server "${this.name}" (${this.config.command}) failed: ${error.message}`, { cause: error });
294136
+ for (const pending of this.pending.values()) {
294137
+ clearTimeout(pending.timer);
294138
+ pending.reject(wrapped);
294139
+ }
294140
+ this.pending.clear();
294141
+ this.process = void 0;
294142
+ if (this.config.restartOnCrash !== false && !this.stopped) this.startPromise = void 0;
294143
+ }
294144
+ };
294145
+ }));
294146
+ //#endregion
294147
+ //#region ../../packages/agent-core/src/lsp/manager.ts
294148
+ function methodForOperation(operation) {
294149
+ switch (operation) {
294150
+ case "definition": return "textDocument/definition";
294151
+ case "references": return "textDocument/references";
294152
+ case "hover": return "textDocument/hover";
294153
+ case "document_symbols": return "textDocument/documentSymbol";
294154
+ case "implementation": return "textDocument/implementation";
294155
+ }
294156
+ }
294157
+ function oneBasedToZeroBased(value, name) {
294158
+ if (value === void 0 || !Number.isInteger(value) || value < 1) throw new Error(`LSP operation requires a one-based ${name}`);
294159
+ return value - 1;
294160
+ }
294161
+ function positionFrom(input) {
294162
+ return {
294163
+ line: oneBasedToZeroBased(input.line, "line"),
294164
+ character: oneBasedToZeroBased(input.character, "character")
294165
+ };
294166
+ }
294167
+ var LspManager;
294168
+ var init_manager$1 = __esmMin((() => {
294169
+ init_client$1();
294170
+ LspManager = class {
294171
+ options;
294172
+ clients = /* @__PURE__ */ new Map();
294173
+ openedDocuments = /* @__PURE__ */ new Map();
294174
+ constructor(options) {
294175
+ this.options = options;
294176
+ }
294177
+ serverNames() {
294178
+ return Object.keys(this.options.servers).toSorted();
294179
+ }
294180
+ runningServerNames() {
294181
+ return [...this.clients.entries()].filter(([, client]) => client.running).map(([name]) => name).toSorted();
294182
+ }
294183
+ async diagnosticsAfterChange(filePath) {
294184
+ const resolvedPath = path.resolve(this.options.cwd, filePath);
294185
+ if (!this.supportsPath(resolvedPath)) return void 0;
294186
+ const diagnostics = await this.request({
294187
+ operation: "diagnostics",
294188
+ path: resolvedPath
294189
+ });
294190
+ return Array.isArray(diagnostics) ? diagnostics : [];
294191
+ }
294192
+ async request(input) {
294193
+ if (input.operation === "workspace_symbols") {
294194
+ if (input.query === void 0) throw new Error("workspace_symbols requires query");
294195
+ const results = await Promise.allSettled(this.serverNames().map(async (name) => {
294196
+ const result = await this.client(name, this.options.servers[name]).request("workspace/symbol", { query: input.query });
294197
+ return Array.isArray(result) ? result : [];
294198
+ }));
294199
+ const fulfilled = results.filter((result) => result.status === "fulfilled");
294200
+ if (fulfilled.length === 0) throw results.find((result) => result.status === "rejected")?.reason ?? /* @__PURE__ */ new Error("No LSP servers are configured");
294201
+ return fulfilled.flatMap((result) => result.value);
294202
+ }
294203
+ const documentPath = this.requireDocumentPath(input.path);
294204
+ const { client, languageId } = this.clientForPath(documentPath);
294205
+ const uri = pathToFileURL(documentPath).href;
294206
+ await this.openOrUpdateDocument(client, uri, documentPath, languageId);
294207
+ if (input.operation === "diagnostics") return client.waitForDiagnostics(uri);
294208
+ const method = methodForOperation(input.operation);
294209
+ const params = input.operation === "document_symbols" ? { textDocument: { uri } } : input.operation === "references" ? {
294210
+ textDocument: { uri },
294211
+ position: positionFrom(input),
294212
+ context: { includeDeclaration: true }
294213
+ } : {
294214
+ textDocument: { uri },
294215
+ position: positionFrom(input)
294216
+ };
294217
+ return client.request(method, params);
294218
+ }
294219
+ async shutdown() {
294220
+ await Promise.allSettled([...this.clients.values()].map((client) => client.shutdown()));
294221
+ this.clients.clear();
294222
+ this.openedDocuments.clear();
294223
+ }
294224
+ clientForPath(filePath) {
294225
+ const extension = path.extname(filePath).toLowerCase();
294226
+ for (const [name, config] of Object.entries(this.options.servers)) {
294227
+ const languageId = config.extensionToLanguage[extension];
294228
+ if (languageId !== void 0) return {
294229
+ client: this.client(name, config),
294230
+ languageId
294231
+ };
294232
+ }
294233
+ throw new Error(`No LSP server is configured for "${extension || path.basename(filePath)}". Configured servers: ${this.serverNames().join(", ") || "none"}`);
294234
+ }
294235
+ supportsPath(filePath) {
294236
+ const extension = path.extname(filePath).toLowerCase();
294237
+ return Object.values(this.options.servers).some((config) => config.extensionToLanguage[extension] !== void 0);
294238
+ }
294239
+ client(name, config) {
294240
+ const existing = this.clients.get(name);
294241
+ if (existing !== void 0) return existing;
294242
+ const client = new StdioLspClient(name, config, this.options.cwd);
294243
+ this.clients.set(name, client);
294244
+ return client;
294245
+ }
294246
+ async openOrUpdateDocument(client, uri, filePath, languageId) {
294247
+ await client.start();
294248
+ const text = await readFile(filePath, "utf8");
294249
+ const key = `${client.name}\0${uri}`;
294250
+ const current = this.openedDocuments.get(key);
294251
+ if (current === void 0 || current.generation !== client.generation) {
294252
+ this.openedDocuments.set(key, {
294253
+ text,
294254
+ version: 1,
294255
+ generation: client.generation
294256
+ });
294257
+ client.notify("textDocument/didOpen", { textDocument: {
294258
+ uri,
294259
+ languageId,
294260
+ version: 1,
294261
+ text
294262
+ } });
294263
+ return;
294264
+ }
294265
+ if (current.text === text) return;
294266
+ const version = current.version + 1;
294267
+ this.openedDocuments.set(key, {
294268
+ text,
294269
+ version,
294270
+ generation: client.generation
294271
+ });
294272
+ client.clearDiagnostics(uri);
294273
+ client.notify("textDocument/didChange", {
294274
+ textDocument: {
294275
+ uri,
294276
+ version
294277
+ },
294278
+ contentChanges: [{ text }]
294279
+ });
294280
+ }
294281
+ requireDocumentPath(input) {
294282
+ if (input === void 0 || input.trim().length === 0) throw new Error("LSP operation requires path");
294283
+ return path.resolve(this.options.cwd, input);
294284
+ }
294285
+ };
294286
+ }));
294287
+ //#endregion
294288
+ //#region ../../packages/agent-core/src/lsp/index.ts
294289
+ var init_lsp = __esmMin((() => {
294290
+ init_manager$1();
294291
+ }));
294292
+ //#endregion
293748
294293
  //#region ../../packages/agent-core/src/session/index.ts
293749
294294
  async function waitForSettlementOrTimeout(promise, timeoutMs) {
293750
294295
  let timeout;
@@ -293789,6 +294334,7 @@ var init_session$1 = __esmMin((() => {
293789
294334
  init_flags();
293790
294335
  init_abort();
293791
294336
  init_vision_reader();
294337
+ init_lsp();
293792
294338
  init_subagent_host();
293793
294339
  BACKGROUND_KEEP_ALIVE_ON_EXIT_ENV = "BLUN_BACKGROUND_KEEP_ALIVE_ON_EXIT";
293794
294340
  ACTIVE_TURN_CLOSE_TIMEOUT_MS = 8e3;
@@ -293800,6 +294346,7 @@ var init_session$1 = __esmMin((() => {
293800
294346
  skills;
293801
294347
  agents = /* @__PURE__ */ new Map();
293802
294348
  mcp;
294349
+ lsp;
293803
294350
  log;
293804
294351
  logHandle;
293805
294352
  hookEngine;
@@ -293849,6 +294396,10 @@ var init_session$1 = __esmMin((() => {
293849
294396
  log: this.log,
293850
294397
  stdioCwd: options.kaos.getcwd()
293851
294398
  });
294399
+ this.lsp = options.lspServers === void 0 || Object.keys(options.lspServers).length === 0 ? void 0 : new LspManager({
294400
+ cwd: options.kaos.getcwd(),
294401
+ servers: options.lspServers
294402
+ });
293852
294403
  this.mcp.onStatusChange((entry) => {
293853
294404
  this.onMcpServerStatusChange(entry);
293854
294405
  });
@@ -293951,7 +294502,7 @@ var init_session$1 = __esmMin((() => {
293951
294502
  await this.triggerSessionEnd("exit");
293952
294503
  } finally {
293953
294504
  try {
293954
- await this.mcp.shutdown();
294505
+ await Promise.allSettled([this.mcp.shutdown(), this.lsp?.shutdown()]);
293955
294506
  } finally {
293956
294507
  await this.logHandle?.close();
293957
294508
  }
@@ -293963,7 +294514,7 @@ var init_session$1 = __esmMin((() => {
293963
294514
  await this.flushMetadata();
293964
294515
  } finally {
293965
294516
  try {
293966
- await this.mcp.shutdown();
294517
+ await Promise.allSettled([this.mcp.shutdown(), this.lsp?.shutdown()]);
293967
294518
  } finally {
293968
294519
  await this.logHandle?.close();
293969
294520
  }
@@ -294351,7 +294902,10 @@ var init_session$1 = __esmMin((() => {
294351
294902
  ...config,
294352
294903
  type,
294353
294904
  kaos: this.toolKaos.withCwd(cwd),
294354
- toolServices: this.options.toolServices,
294905
+ toolServices: {
294906
+ ...this.options.toolServices,
294907
+ lsp: this.lsp
294908
+ },
294355
294909
  config: this.options.config,
294356
294910
  blunHomeDir: this.options.blunHomeDir,
294357
294911
  homedir,
@@ -294650,6 +295204,7 @@ async function parseManifest(pluginRoot) {
294650
295204
  skills,
294651
295205
  sessionStart: readSessionStart(raw["sessionStart"], diagnostics),
294652
295206
  mcpServers: await readMcpServers(pluginRoot, raw["mcpServers"], diagnostics),
295207
+ lspServers: await readLspServers(pluginRoot, raw["lspServers"], diagnostics),
294653
295208
  hooks: readHooks(raw["hooks"], diagnostics),
294654
295209
  commands: await readCommands(pluginRoot, raw["commands"], diagnostics),
294655
295210
  interface: readInterface(raw["interface"]),
@@ -294798,6 +295353,114 @@ async function readMcpServers(pluginRoot, raw, diagnostics) {
294798
295353
  }
294799
295354
  return Object.keys(out).length === 0 ? void 0 : out;
294800
295355
  }
295356
+ async function readLspServers(pluginRoot, manifestValue, diagnostics) {
295357
+ let raw = manifestValue;
295358
+ if (raw === void 0) {
295359
+ const configPath = path.join(pluginRoot, ".lsp.json");
295360
+ if (!await isFile$1(configPath)) return void 0;
295361
+ try {
295362
+ raw = JSON.parse(await readFile(configPath, "utf8"));
295363
+ } catch (error) {
295364
+ diagnostics.push({
295365
+ severity: "warn",
295366
+ message: `Failed to parse .lsp.json: ${error.message}`
295367
+ });
295368
+ return;
295369
+ }
295370
+ }
295371
+ if (!isObject$4(raw)) {
295372
+ diagnostics.push({
295373
+ severity: "warn",
295374
+ message: "\"lspServers\" must be an object"
295375
+ });
295376
+ return;
295377
+ }
295378
+ const out = {};
295379
+ for (const [name, value] of Object.entries(raw)) {
295380
+ const config = await normalizePluginLspServer(pluginRoot, name, value, diagnostics);
295381
+ if (config !== void 0) out[name] = config;
295382
+ }
295383
+ return out;
295384
+ }
295385
+ async function normalizePluginLspServer(pluginRoot, name, raw, diagnostics) {
295386
+ const field = `lspServers.${name}`;
295387
+ if (!isObject$4(raw)) {
295388
+ diagnostics.push({
295389
+ severity: "warn",
295390
+ message: `"${field}" must be an object`
295391
+ });
295392
+ return;
295393
+ }
295394
+ let command = stringField$3(raw, "command");
295395
+ if (command === void 0) {
295396
+ diagnostics.push({
295397
+ severity: "warn",
295398
+ message: `"${field}.command" is required`
295399
+ });
295400
+ return;
295401
+ }
295402
+ const extensionToLanguage = stringRecordField(raw["extensionToLanguage"]);
295403
+ if (extensionToLanguage === void 0 || Object.keys(extensionToLanguage).length === 0) {
295404
+ diagnostics.push({
295405
+ severity: "warn",
295406
+ message: `"${field}.extensionToLanguage" must map at least one extension to a language`
295407
+ });
295408
+ return;
295409
+ }
295410
+ if (command.startsWith("./")) {
295411
+ command = await resolvePluginPathField({
295412
+ pluginRoot,
295413
+ field: `${field}.command`,
295414
+ value: command,
295415
+ diagnostics
295416
+ });
295417
+ if (command === void 0) return void 0;
295418
+ } else if (command.includes("/") || path.isAbsolute(command)) {
295419
+ diagnostics.push({
295420
+ severity: "warn",
295421
+ message: `"${field}.command" must be a PATH command or start with "./"`
295422
+ });
295423
+ return;
295424
+ }
295425
+ let workspaceFolder = stringField$3(raw, "workspaceFolder");
295426
+ if (workspaceFolder !== void 0) {
295427
+ workspaceFolder = await resolvePluginPathField({
295428
+ pluginRoot,
295429
+ field: `${field}.workspaceFolder`,
295430
+ value: workspaceFolder,
295431
+ diagnostics
295432
+ });
295433
+ if (workspaceFolder === void 0) return void 0;
295434
+ }
295435
+ const args = stringArrayField$1(raw, "args");
295436
+ if (raw["args"] !== void 0 && args === void 0) {
295437
+ diagnostics.push({
295438
+ severity: "warn",
295439
+ message: `"${field}.args" must be a string[]`
295440
+ });
295441
+ return;
295442
+ }
295443
+ const env = stringRecordField(raw["env"]);
295444
+ if (raw["env"] !== void 0 && env === void 0) {
295445
+ diagnostics.push({
295446
+ severity: "warn",
295447
+ message: `"${field}.env" must contain string values`
295448
+ });
295449
+ return;
295450
+ }
295451
+ return {
295452
+ command,
295453
+ args,
295454
+ extensionToLanguage,
295455
+ env,
295456
+ initializationOptions: objectField(raw["initializationOptions"]),
295457
+ settings: objectField(raw["settings"]),
295458
+ workspaceFolder,
295459
+ startupTimeoutMs: positiveNumberField(raw["startupTimeoutMs"]),
295460
+ shutdownTimeoutMs: positiveNumberField(raw["shutdownTimeoutMs"]),
295461
+ restartOnCrash: booleanField(raw["restartOnCrash"])
295462
+ };
295463
+ }
294801
295464
  function readHooks(raw, diagnostics) {
294802
295465
  if (raw === void 0) return void 0;
294803
295466
  if (!Array.isArray(raw)) {
@@ -294945,6 +295608,19 @@ function stringArrayField$1(raw, key) {
294945
295608
  if (!Array.isArray(value) || !value.every((entry) => typeof entry === "string")) return;
294946
295609
  return value;
294947
295610
  }
295611
+ function stringRecordField(value) {
295612
+ if (!isObject$4(value) || !Object.values(value).every((entry) => typeof entry === "string")) return;
295613
+ return value;
295614
+ }
295615
+ function objectField(value) {
295616
+ return isObject$4(value) ? value : void 0;
295617
+ }
295618
+ function positiveNumberField(value) {
295619
+ return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : void 0;
295620
+ }
295621
+ function booleanField(value) {
295622
+ return typeof value === "boolean" ? value : void 0;
295623
+ }
294948
295624
  function isObject$4(value) {
294949
295625
  return typeof value === "object" && value !== null && !Array.isArray(value);
294950
295626
  }
@@ -295474,7 +296150,7 @@ function pluginMcpServerInfo(record, name, config) {
295474
296150
  }
295475
296151
  function publicPluginManifest(manifest) {
295476
296152
  if (manifest === void 0) return void 0;
295477
- const { mcpServers: _privateMcpServers, ...publicManifest } = manifest;
296153
+ const { mcpServers: _privateMcpServers, lspServers: _privateLspServers, ...publicManifest } = manifest;
295478
296154
  return publicManifest;
295479
296155
  }
295480
296156
  function sanitizePluginOriginalSource(value) {
@@ -295724,6 +296400,14 @@ var init_manager = __esmMin((() => {
295724
296400
  enabledMcpServers() {
295725
296401
  return Object.fromEntries(this.mcpServerConfigs().filter((entry) => entry.config.enabled !== false).map((entry) => [entry.runtimeName, entry.config]));
295726
296402
  }
296403
+ enabledLspServers() {
296404
+ const out = {};
296405
+ for (const record of this.records.values()) {
296406
+ if (!this.runtimeEnabled(record) || record.state !== "ok" || record.manifest === void 0) continue;
296407
+ for (const [name, config] of Object.entries(record.manifest.lspServers ?? {})) out[`plugin-${record.id}:${name}`] = config;
296408
+ }
296409
+ return out;
296410
+ }
295727
296411
  /** All installed plugin MCPs, including disabled plugin/server entries. */
295728
296412
  mcpServerConfigs() {
295729
296413
  const out = [];
@@ -312945,6 +313629,7 @@ var init_core_impl = __esmMin((() => {
312945
313629
  permissionRules: config.permission?.rules,
312946
313630
  skills: this.resolveSessionSkillConfig(config),
312947
313631
  mcpConfig,
313632
+ lspServers: this.plugins.enabledLspServers(),
312948
313633
  experimentalFlags: this.experimentalFlags,
312949
313634
  telemetry: sessionTelemetry,
312950
313635
  pluginSessionStarts,
@@ -313041,6 +313726,7 @@ var init_core_impl = __esmMin((() => {
313041
313726
  permissionRules: config.permission?.rules,
313042
313727
  skills: this.resolveSessionSkillConfig(config),
313043
313728
  mcpConfig,
313729
+ lspServers: this.plugins.enabledLspServers(),
313044
313730
  experimentalFlags: this.experimentalFlags,
313045
313731
  telemetry: withTelemetryContext$1(this.telemetry, { sessionId: summary.id }),
313046
313732
  initializeMainAgent: false,
@@ -414965,6 +415651,9 @@ function resolveTelegramBridgeLaunch(bridge, options = {}) {
414965
415651
  }
414966
415652
  };
414967
415653
  }
415654
+ function queueIdentity(stats) {
415655
+ return `${stats.dev}:${stats.ino}:${stats.birthtimeMs}`;
415656
+ }
414968
415657
  function telegramStateDir() {
414969
415658
  return process.env["BLUN_TELEGRAM_STATE_DIR"] ?? join(homedir(), ".blun", "channels", "telegram");
414970
415659
  }
@@ -415036,7 +415725,10 @@ var TelegramChannelController = class {
415036
415725
  activationTimer;
415037
415726
  handoffTimer;
415038
415727
  offset = 0;
415039
- remainder = "";
415728
+ checkpointOffset = 0;
415729
+ queueFileId = "";
415730
+ remainder = Buffer.alloc(0);
415731
+ pendingQueueLines = [];
415040
415732
  started = false;
415041
415733
  stopped = false;
415042
415734
  activeOwner = false;
@@ -415078,6 +415770,9 @@ var TelegramChannelController = class {
415078
415770
  get queueFile() {
415079
415771
  return join(this.dir, "inbound-queue.jsonl");
415080
415772
  }
415773
+ get queueCheckpointFile() {
415774
+ return join(this.dir, "inbound-queue.checkpoint.json");
415775
+ }
415081
415776
  get botPidFile() {
415082
415777
  return join(this.dir, "bot.pid");
415083
415778
  }
@@ -415106,9 +415801,19 @@ var TelegramChannelController = class {
415106
415801
  this.heartbeatTimer = setInterval(() => this.writeLease(), HEARTBEAT_MS);
415107
415802
  this.heartbeatTimer.unref();
415108
415803
  try {
415109
- this.offset = statSync(this.queueFile).size;
415804
+ const stats = statSync(this.queueFile);
415805
+ this.queueFileId = queueIdentity(stats);
415806
+ const checkpoint = this.readQueueCheckpoint();
415807
+ if (checkpoint?.fileId === this.queueFileId && checkpoint.offset >= 0 && checkpoint.offset <= stats.size) this.offset = checkpoint.offset;
415808
+ else {
415809
+ this.offset = stats.size;
415810
+ this.writeQueueCheckpoint(this.offset);
415811
+ }
415812
+ this.checkpointOffset = this.offset;
415110
415813
  } catch {
415111
415814
  this.offset = 0;
415815
+ this.checkpointOffset = 0;
415816
+ this.queueFileId = "";
415112
415817
  }
415113
415818
  this.tailTimer = setInterval(() => this.drainQueue(), TAIL_POLL_MS);
415114
415819
  this.tailTimer.unref();
@@ -415286,24 +415991,34 @@ var TelegramChannelController = class {
415286
415991
  this.loseOwnership();
415287
415992
  return;
415288
415993
  }
415289
- let size;
415994
+ let stats;
415290
415995
  try {
415291
- size = statSync(this.queueFile).size;
415996
+ stats = statSync(this.queueFile);
415292
415997
  } catch {
415293
415998
  return;
415294
415999
  }
415295
- if (size < this.offset) {
416000
+ const size = stats.size;
416001
+ const fileId = queueIdentity(stats);
416002
+ if (this.queueFileId === "") {
416003
+ this.queueFileId = fileId;
416004
+ this.writeQueueCheckpoint(this.checkpointOffset);
416005
+ } else if (fileId !== this.queueFileId || size < this.offset) {
416006
+ this.queueFileId = fileId;
415296
416007
  this.offset = 0;
415297
- this.remainder = "";
416008
+ this.checkpointOffset = 0;
416009
+ this.remainder = Buffer.alloc(0);
416010
+ this.pendingQueueLines.length = 0;
416011
+ this.writeQueueCheckpoint(0);
415298
416012
  }
415299
416013
  if (size === this.offset) return;
415300
416014
  let chunk;
416015
+ const chunkStart = this.offset;
415301
416016
  try {
415302
416017
  const fd = openSync(this.queueFile, "r");
415303
416018
  try {
415304
416019
  const buf = Buffer.alloc(size - this.offset);
415305
416020
  const read = readSync(fd, buf, 0, buf.length, this.offset);
415306
- chunk = buf.subarray(0, read).toString("utf8");
416021
+ chunk = buf.subarray(0, read);
415307
416022
  this.offset += read;
415308
416023
  } finally {
415309
416024
  closeSync(fd);
@@ -415311,11 +416026,54 @@ var TelegramChannelController = class {
415311
416026
  } catch {
415312
416027
  return;
415313
416028
  }
415314
- const lines = (this.remainder + chunk).split("\n");
415315
- this.remainder = lines.pop() ?? "";
415316
- for (const line of lines) {
416029
+ const combinedStart = chunkStart - this.remainder.byteLength;
416030
+ const combined = this.remainder.byteLength === 0 ? chunk : Buffer.concat([this.remainder, chunk]);
416031
+ let lineStart = 0;
416032
+ for (let index = 0; index < combined.byteLength; index += 1) {
416033
+ if (combined[index] !== 10) continue;
416034
+ const line = combined.subarray(lineStart, index).toString("utf8");
416035
+ const pending = {
416036
+ endOffset: combinedStart + index + 1,
416037
+ acknowledged: false
416038
+ };
416039
+ this.pendingQueueLines.push(pending);
415317
416040
  const envelope = parseChannelEnvelope(line);
415318
- if (envelope !== void 0) this.host.inject(envelope);
416041
+ if (envelope === void 0) this.acknowledgeQueueLine(pending);
416042
+ else this.host.inject(envelope, () => this.acknowledgeQueueLine(pending));
416043
+ lineStart = index + 1;
416044
+ }
416045
+ this.remainder = combined.subarray(lineStart);
416046
+ }
416047
+ acknowledgeQueueLine(pending) {
416048
+ if (this.stopped || !this.ownsChannel() || pending.acknowledged) return;
416049
+ pending.acknowledged = true;
416050
+ let nextOffset = this.checkpointOffset;
416051
+ while (this.pendingQueueLines[0]?.acknowledged === true) nextOffset = this.pendingQueueLines.shift().endOffset;
416052
+ if (nextOffset === this.checkpointOffset) return;
416053
+ this.checkpointOffset = nextOffset;
416054
+ this.writeQueueCheckpoint(nextOffset);
416055
+ }
416056
+ readQueueCheckpoint() {
416057
+ try {
416058
+ const parsed = JSON.parse(readFileSync(this.queueCheckpointFile, "utf8"));
416059
+ if (parsed.version === 1 && typeof parsed.fileId === "string" && parsed.fileId.length > 0 && Number.isSafeInteger(parsed.offset) && (parsed.offset ?? -1) >= 0) return parsed;
416060
+ } catch {}
416061
+ }
416062
+ writeQueueCheckpoint(offset) {
416063
+ if (this.queueFileId.length === 0) return;
416064
+ const temporary = join(this.dir, `.inbound-queue.checkpoint.${process.pid}.${this.ownerId}.tmp`);
416065
+ try {
416066
+ const checkpoint = {
416067
+ version: 1,
416068
+ fileId: this.queueFileId,
416069
+ offset
416070
+ };
416071
+ writeFileSync(temporary, `${JSON.stringify(checkpoint)}\n`, { mode: 384 });
416072
+ renameSync(temporary, this.queueCheckpointFile);
416073
+ } catch (error) {
416074
+ this.host.warn(uiText("telegramChannel.leaseWriteFailed", { error: String(error) }));
416075
+ } finally {
416076
+ rmSync(temporary, { force: true });
415319
416077
  }
415320
416078
  }
415321
416079
  /**
@@ -510529,6 +511287,7 @@ var BlunTUI = class {
510529
511287
  };
510530
511288
  try {
510531
511289
  if (!(await this.harness.withInteractiveAgent(item.agentId ?? "main", () => session.steerActive(input, expectedTurnId === void 0 ? {} : { expectedTurnId }))).accepted) return restoreHead();
511290
+ item.channelAcknowledge?.();
510532
511291
  } catch (error) {
510533
511292
  return restoreHead(error);
510534
511293
  }
@@ -510670,12 +511429,12 @@ var BlunTUI = class {
510670
511429
  * queuedMessages mechanic as typed input; a completed tool/step boundary
510671
511430
  * steers one FIFO head into the active turn without interrupting it.
510672
511431
  */
510673
- injectChannelMessage(envelope) {
511432
+ injectChannelMessage(envelope, acknowledge) {
510674
511433
  injectChannelEnvelope({
510675
511434
  canDeliver: () => this.session !== void 0 && this.state.appState.model.trim().length > 0,
510676
511435
  isBusy: () => this.state.queuedMessages.length > 0 || this.queueSteerInFlight !== void 0 || this.deferUserMessages || this.state.appState.streamingPhase !== "idle" || this.state.appState.isCompacting,
510677
511436
  deliverNow: (modelInput, displayText, origin, contextOnly) => {
510678
- this.sendChannelMessageInternal(this.requireSession(), modelInput, displayText, origin, envelope.meta.chat_id, envelope.meta["image_path"], contextOnly);
511437
+ this.sendChannelMessageInternal(this.requireSession(), modelInput, displayText, origin, envelope.meta.chat_id, envelope.meta["image_path"], contextOnly, false, acknowledge);
510679
511438
  },
510680
511439
  enqueue: (modelInput, displayText, origin, contextOnly) => {
510681
511440
  this.state.queuedMessages.push({
@@ -510686,6 +511445,7 @@ var BlunTUI = class {
510686
511445
  mode: "channel",
510687
511446
  channelChatId: envelope.meta.chat_id,
510688
511447
  channelContextOnly: contextOnly,
511448
+ channelAcknowledge: acknowledge,
510689
511449
  ...envelope.meta["image_path"] !== void 0 ? { channelImagePath: envelope.meta["image_path"] } : {}
510690
511450
  });
510691
511451
  this.syncChannelQueueDeadline();
@@ -510702,6 +511462,7 @@ var BlunTUI = class {
510702
511462
  content: displayText,
510703
511463
  origin
510704
511464
  });
511465
+ acknowledge?.();
510705
511466
  this.state.ui.requestRender();
510706
511467
  },
510707
511468
  reportUndeliverable: (reason) => {
@@ -510709,7 +511470,7 @@ var BlunTUI = class {
510709
511470
  }
510710
511471
  }, envelope, this.channelPreamble);
510711
511472
  }
510712
- sendChannelMessageInternal(session, modelInput, displayText, origin, channelChatId, channelImagePath, contextOnly = false, transcriptRendered = false) {
511473
+ sendChannelMessageInternal(session, modelInput, displayText, origin, channelChatId, channelImagePath, contextOnly = false, transcriptRendered = false, acknowledge) {
510713
511474
  if (!transcriptRendered) this.appendTranscriptEntry({
510714
511475
  id: nextTranscriptId(),
510715
511476
  kind: "user",
@@ -510719,12 +511480,14 @@ var BlunTUI = class {
510719
511480
  origin
510720
511481
  });
510721
511482
  this.beginSessionRequest();
510722
- if (channelChatId !== void 0) this.pendingChannelReplyGuard = {
511483
+ const previousGuard = this.pendingChannelReplyGuard;
511484
+ const installedGuard = channelChatId === void 0 ? void 0 : {
510723
511485
  chatId: channelChatId,
510724
511486
  outboxMarker: outboxMarker(),
510725
511487
  transcriptStart: this.state.transcriptEntries.length,
510726
511488
  contextOnly
510727
511489
  };
511490
+ if (installedGuard !== void 0) this.pendingChannelReplyGuard = installedGuard;
510728
511491
  const imagePart = channelImagePath !== void 0 ? buildChannelImagePart(channelImagePath) : void 0;
510729
511492
  const visionReaderEnabled = isExperimentalFlagEnabled("vision_reader");
510730
511493
  this.setAppState({
@@ -510735,7 +511498,29 @@ var BlunTUI = class {
510735
511498
  type: "text",
510736
511499
  text: modelInput
510737
511500
  }, imagePart] : modelInput;
510738
- session.promptAccepted(promptInput).catch((error) => {
511501
+ session.promptAccepted(promptInput).then((result) => {
511502
+ if (result.accepted) {
511503
+ acknowledge?.();
511504
+ return;
511505
+ }
511506
+ if (installedGuard !== void 0 && this.pendingChannelReplyGuard === installedGuard) this.pendingChannelReplyGuard = previousGuard;
511507
+ this.state.queuedMessages = [{
511508
+ text: modelInput,
511509
+ displayText,
511510
+ origin,
511511
+ agentId: this.harness.interactiveAgentId,
511512
+ mode: "channel",
511513
+ channelChatId,
511514
+ channelContextOnly: contextOnly,
511515
+ channelTranscriptRendered: true,
511516
+ channelAcknowledge: acknowledge,
511517
+ ...channelImagePath === void 0 ? {} : { channelImagePath }
511518
+ }, ...this.state.queuedMessages];
511519
+ this.syncChannelQueueDeadline();
511520
+ this.track("input_queue");
511521
+ this.updateQueueDisplay();
511522
+ this.state.ui.requestRender();
511523
+ }).catch((error) => {
510739
511524
  this.failSessionRequest(uiText("blunTui.session.sendFailed", { error: formatErrorMessage$2(error) }));
510740
511525
  });
510741
511526
  this.updateQueueDisplay();
@@ -510779,8 +511564,8 @@ var BlunTUI = class {
510779
511564
  }
510780
511565
  let controller;
510781
511566
  controller = new TelegramChannelController({
510782
- inject: (envelope) => {
510783
- this.injectChannelMessage(envelope);
511567
+ inject: (envelope, acknowledge) => {
511568
+ this.injectChannelMessage(envelope, acknowledge);
510784
511569
  },
510785
511570
  warn: (message) => {
510786
511571
  this.showStatus(message, "warning");
@@ -510904,7 +511689,7 @@ var BlunTUI = class {
510904
511689
  const activeSession = this.session ?? session;
510905
511690
  if (item.mode === "channel") {
510906
511691
  this.harness.withInteractiveAgent(item.agentId ?? "main", () => {
510907
- this.sendChannelMessageInternal(activeSession, item.text, item.displayText ?? "", item.origin, item.channelChatId, item.channelImagePath, item.channelContextOnly, item.channelTranscriptRendered);
511692
+ this.sendChannelMessageInternal(activeSession, item.text, item.displayText ?? "", item.origin, item.channelChatId, item.channelImagePath, item.channelContextOnly, item.channelTranscriptRendered, item.channelAcknowledge);
510908
511693
  });
510909
511694
  return;
510910
511695
  }
@@ -511263,7 +512048,7 @@ var BlunTUI = class {
511263
512048
  streamingPhase: "idle"
511264
512049
  });
511265
512050
  if (!this.preserveQueueAcrossSessionReset) {
511266
- this.state.queuedMessages = [];
512051
+ this.state.queuedMessages = this.state.queuedMessages.filter((item) => item.mode === "channel");
511267
512052
  this.queueFlushBatchRemaining = 0;
511268
512053
  this.queueSteerInFlight = void 0;
511269
512054
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blun-king-cli",
3
- "version": "9.1.25",
3
+ "version": "9.1.26",
4
4
  "description": "BLUN CLI - your own AI agent with a Telegram channel. Get it done. With BLUN.",
5
5
  "license": "MIT",
6
6
  "bin": {