blun-king-cli 9.1.422 → 9.1.424

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.
@@ -29,6 +29,7 @@ const { CORE_LOADED_MESSAGE } = require('./core-bootstrap');
29
29
  const { prepareManagedNodeRuntime } = require('./node-runtime');
30
30
  const { repairConfiguredNativeModules } = require('./native-module-repair');
31
31
  const { startMnemoConnectHeartbeat } = require('./mnemo-connect-heartbeat.cjs');
32
+ const { recordRuntimeExit } = require('./runtime-exit-ledger.cjs');
32
33
  const { acquireSharedRuntimeLease } = require('./update-lease');
33
34
  const { runExplicitUpdate, runUpdateNotice } = require('./update-notice');
34
35
  const {
@@ -218,8 +219,35 @@ async function superviseProtectedCore(args, env, cwd, releaseLauncherLeases, opt
218
219
  const loaded = await core.loaded;
219
220
  if (loaded) await releaseLauncherLeases();
220
221
  const result = await core.completed;
221
- if (result.error) throw result.error;
222
- return exitCodeForChild(result, loaded);
222
+ if (result.error) {
223
+ recordRuntimeExit({
224
+ homeDir: env.BLUN_HOME,
225
+ source: 'launcher',
226
+ kind: 'child-error',
227
+ exitCode: 1,
228
+ phase: loaded ? 'runtime' : 'startup',
229
+ cliVersion: env.BLUN_PUBLIC_PACKAGE_VERSION,
230
+ profile: env.BLUN_PROFILE,
231
+ loaded,
232
+ childPid: core.child?.pid,
233
+ error: result.error,
234
+ });
235
+ throw result.error;
236
+ }
237
+ const exitCode = exitCodeForChild(result, loaded);
238
+ recordRuntimeExit({
239
+ homeDir: env.BLUN_HOME,
240
+ source: 'launcher',
241
+ kind: 'child-exit',
242
+ exitCode,
243
+ signal: result.signal,
244
+ phase: loaded ? 'runtime' : 'startup',
245
+ cliVersion: env.BLUN_PUBLIC_PACKAGE_VERSION,
246
+ profile: env.BLUN_PROFILE,
247
+ loaded,
248
+ childPid: core.child?.pid,
249
+ });
250
+ return exitCode;
223
251
  }
224
252
 
225
253
  function installTelegramForProfile(packageRoot, blunDir) {
@@ -14,7 +14,8 @@ const NON_ACTIONABLE_USER_ORIGINS = new Set([
14
14
  'skill_activation',
15
15
  ]);
16
16
  const STOP_WORDS = new Set([
17
- 'aber', 'alle', 'alles', 'auch', 'eine', 'einem', 'einen', 'einer', 'eines',
17
+ 'aber', 'alle', 'alles', 'auch', 'das', 'dem', 'den', 'der', 'des', 'die',
18
+ 'ein', 'eine', 'einem', 'einen', 'einer', 'eines',
18
19
  'fuer', 'haben', 'hier', 'immer', 'jetzt', 'kann', 'machen', 'mehr', 'mich',
19
20
  'nicht', 'noch', 'oder', 'sein', 'sind', 'soll', 'und', 'uns', 'von', 'was',
20
21
  'wenn', 'werden', 'wie', 'with', 'that', 'this', 'from', 'have', 'into',
@@ -22,8 +23,36 @@ const STOP_WORDS = new Set([
22
23
  ]);
23
24
  const REUSABLE_HEADING = /\b(rule|regel|condition|bedingung|countercheck|gegenprobe|trigger|ausloeser|auslöser)\b/iu;
24
25
 
26
+ const SEMANTIC_SIGNAL_RULES = Object.freeze([
27
+ ['problem:reliability', /\b(?:error|failure|failed|broken|crash(?:ed)?|timeout|unavailable|corrupt(?:ed)?|dropped|missing|lost|fehler|scheiter\w*|absturz|ausfall|kaputt|defekt|fehlend|verloren|unerreichbar|abbruch)\b/iu],
28
+ ['problem:delivery', /\b(?:deliver\w*|receiv\w*|message\w*|repl(?:y|ies)|inbox|outbox|send(?:ing|s|t)?|consum\w*|zustell\w*|empfang\w*|nachricht\w*|meldung\w*|senden|bekomm\w*|antwort\w*)\b/iu],
29
+ ['problem:stagnation', /\b(?:loop\w*|repeat\w*|stuck|hang(?:ing)?|freeze|frozen|idle|no progress|stop(?:s|ped)? consuming|schleife\w*|wiederhol\w*|hang\w*|stillstand|leerlauf|kein fortschritt|taub)\b/iu],
30
+ ['problem:performance', /\b(?:slow|latency|bottleneck|throughput|memory pressure|cpu|vram|langsam\w*|latenz|durchsatz|engpass|speicherdruck)\b/iu],
31
+ ['problem:protocol', /\b(?:schema|contract|payload|protocol|format|version mismatch|manifest mismatch|vertrag|protokoll|versionskonflikt)\b/iu],
32
+ ['problem:capability', /\b(?:capabilit\w*|missing tool|not available|unsupported|fahigkeit\w*|werkzeug fehlt|nicht verfugbar)\b/iu],
33
+ ['area:orchestration', /\b(?:agent\w*|worker\w*|queue\w*|checkpoint\w*|heartbeat\w*|delegat\w*|owner\w*|responsib\w*|task\w*|turn\w*|busy|group\w*|channel\w*|arbeiter\w*|warteschlange\w*|prufpunkt\w*|herzschlag\w*|zustandig\w*|auftrag\w*|zug\w*|beschaftigt|gruppe\w*|gruppen\w*|kanal\w*)\b/iu],
34
+ ['area:memory', /\b(?:memory|recall|remember\w*|context|history|resume|checkpoint|mnemo|gedachtnis|erinner\w*|kontext|verlauf|wiederaufnahme)\b/iu],
35
+ ['area:release', /\b(?:release\w*|rollout\w*|update\w*|updater\w*|npm|package\w*|manifest\w*|version\w*|veroffentlich\w*|paket\w*|integrity|integritat)\b/iu],
36
+ ['area:media', /\b(?:image\w*|video\w*|audio\w*|portrait\w*|render\w*|pixel\w*|media|bild\w*|foto\w*|gesicht\w*|stimme\w*)\b/iu],
37
+ ['risk:validation', /\b(?:verif\w*|validat\w*|test\w*|check\w*|gate\w*|rollback\w*|canary|proof|evidence|sha(?:256|512)?|measur\w*|inspect\w*|(?:ge)?pruf\w*|gegenprobe\w*|beleg\w*|evidenz\w*|mess\w*|kontroll\w*)\b/iu],
38
+ ['action:repair', /\b(?:fix\w*|repair\w*|restore\w*|recover\w*|reparier\w*|beheb\w*|wiederherstell\w*)\b/iu],
39
+ ['action:investigate', /\b(?:inspect\w*|diagnos\w*|measure\w*|trace\w*|check\w*|(?:ge)?pruf\w*|mess\w*|untersuch\w*)\b/iu],
40
+ ]);
41
+
25
42
  function normalizeText(value) {
26
- return String(value || '').normalize('NFKD').replace(/\p{M}/gu, '').toLowerCase();
43
+ return String(value || '')
44
+ .normalize('NFKD')
45
+ .replace(/\p{M}/gu, '')
46
+ .replace(/\u00df/gu, 'ss')
47
+ .toLowerCase();
48
+ }
49
+
50
+ function semanticSignalTags(value) {
51
+ const normalized = normalizeText(value);
52
+ if (!normalized) return [];
53
+ return SEMANTIC_SIGNAL_RULES
54
+ .filter(([, pattern]) => pattern.test(normalized))
55
+ .map(([tag]) => tag);
27
56
  }
28
57
 
29
58
  function terms(value) {
@@ -100,15 +129,25 @@ function isReusableSection(section) {
100
129
  return REUSABLE_HEADING.test(`${section.heading}\n${section.body}`);
101
130
  }
102
131
 
103
- function scoreSection(section, queryTerms) {
132
+ function scoreSection(section, queryTerms, queryTags) {
104
133
  const heading = normalizeText(`${section.parent} ${section.heading}`);
105
134
  const body = normalizeText(section.body);
106
135
  let score = isReusableSection(section) ? 1 : 0;
136
+ let lexicalMatches = 0;
107
137
  for (const term of queryTerms) {
108
- if (heading.includes(term)) score += 6;
109
- if (body.includes(term)) score += 4;
138
+ if (heading.includes(term)) {
139
+ score += 6;
140
+ lexicalMatches += 1;
141
+ }
142
+ if (body.includes(term)) {
143
+ score += 4;
144
+ lexicalMatches += 1;
145
+ }
110
146
  }
111
- return score;
147
+ const sectionTags = new Set(semanticSignalTags(`${heading}\n${body}`));
148
+ const semanticMatches = queryTags.filter((tag) => sectionTags.has(tag));
149
+ score += semanticMatches.length * 2;
150
+ return { lexicalMatches, score, semanticMatches };
112
151
  }
113
152
 
114
153
  function compactSection(section, maxChars) {
@@ -144,16 +183,24 @@ function selectRelevantMistakeSources(sources, query, options = {}) {
144
183
  }))
145
184
  ));
146
185
  const queryTerms = terms(query);
186
+ const queryTags = semanticSignalTags(query);
147
187
  const ranked = sections.map((section) => {
148
188
  const reusable = isReusableSection(section);
149
- const score = scoreSection(section, queryTerms);
189
+ const { lexicalMatches, score, semanticMatches } = scoreSection(section, queryTerms, queryTags);
150
190
  return {
151
191
  ...section,
152
- matched: score > (reusable ? 1 : 0),
192
+ lexicalMatches,
153
193
  reusable,
154
194
  score,
195
+ semanticMatches,
155
196
  };
156
197
  });
198
+ const hasLexicalMatch = ranked.some((section) => section.lexicalMatches > 0);
199
+ for (const section of ranked) {
200
+ section.matched = hasLexicalMatch
201
+ ? section.lexicalMatches > 0
202
+ : section.semanticMatches.length > 0;
203
+ }
157
204
  const hasMatch = ranked.some((section) => section.matched);
158
205
  ranked.sort((left, right) => {
159
206
  if (hasMatch && right.score !== left.score) return right.score - left.score;
@@ -188,6 +235,11 @@ function selectRelevantMistakeSources(sources, query, options = {}) {
188
235
  selected.sort((left, right) => left.ordinal - right.ordinal);
189
236
  const text = [header, ...selected.map((section) => section.rendered)].join('\n\n').slice(0, maxChars);
190
237
  return {
238
+ selected: selected.map((section) => ({
239
+ heading: section.heading,
240
+ reference: normalizedSources[section.sourceIndex]?.reference || '',
241
+ semanticMatches: section.semanticMatches || [],
242
+ })),
191
243
  selectedChars: text.length,
192
244
  selectedSections: selected.length,
193
245
  text,
@@ -209,6 +261,7 @@ module.exports = {
209
261
  RECENT_USER_MESSAGES,
210
262
  isLowInformationMistakeTurn,
211
263
  recentUserText,
264
+ semanticSignalTags,
212
265
  selectRelevantMistakeContent,
213
266
  selectRelevantMistakeSources,
214
267
  };
@@ -0,0 +1,143 @@
1
+ 'use strict';
2
+
3
+ const fs = require('node:fs');
4
+ const os = require('node:os');
5
+ const path = require('node:path');
6
+
7
+ const DEFAULT_MAX_BYTES = 1024 * 1024;
8
+ const DEFAULT_RETAIN_BYTES = 512 * 1024;
9
+ const MAX_MESSAGE_CHARS = 2048;
10
+ const MAX_STACK_CHARS = 8192;
11
+ const MAX_DETAIL_CHARS = 2048;
12
+
13
+ function runtimeExitLedgerPath(homeDir) {
14
+ return path.join(homeDir, 'diagnostics', 'runtime-exits.jsonl');
15
+ }
16
+
17
+ function replaceAllLiteral(value, needle, replacement) {
18
+ if (!needle) return value;
19
+ const lowerValue = process.platform === 'win32' ? value.toLowerCase() : value;
20
+ const lowerNeedle = process.platform === 'win32' ? needle.toLowerCase() : needle;
21
+ let result = '';
22
+ let cursor = 0;
23
+ for (;;) {
24
+ const index = lowerValue.indexOf(lowerNeedle, cursor);
25
+ if (index === -1) return `${result}${value.slice(cursor)}`;
26
+ result += `${value.slice(cursor, index)}${replacement}`;
27
+ cursor = index + needle.length;
28
+ }
29
+ }
30
+
31
+ function sanitizeExitText(input, maxChars = MAX_DETAIL_CHARS) {
32
+ let value = String(input ?? '');
33
+ const homes = new Set([
34
+ os.homedir(),
35
+ process.env.USERPROFILE,
36
+ process.env.HOME,
37
+ ].filter((entry) => typeof entry === 'string' && entry.length > 2));
38
+ for (const home of homes) {
39
+ value = replaceAllLiteral(value, path.normalize(home), '$HOME');
40
+ value = replaceAllLiteral(value, home.replaceAll('\\', '/'), '$HOME');
41
+ }
42
+ value = value
43
+ .replace(/[A-Za-z]:\\Users\\[^\\\s"']+/giu, '$HOME')
44
+ .replace(/\/home\/[^/\s"']+/gu, '$HOME')
45
+ .replace(/(Authorization\s*:\s*Bearer\s+)[^\s,;]+/giu, '$1<redacted>')
46
+ .replace(/\bsk-[A-Za-z0-9_-]{10,}\b/gu, '<redacted>')
47
+ .replace(/\b\d{6,}:[A-Za-z0-9_-]{20,}\b/gu, '<redacted>')
48
+ .replace(/((?:api[_-]?key|access[_-]?token|refresh[_-]?token|token|secret|password)\s*[=:]\s*)[^\s,;}]+/giu, '$1<redacted>')
49
+ .replace(/(https?:\/\/[^\s/:@]+:)[^\s/@]+@/giu, '$1<redacted>@');
50
+ return value.length <= maxChars ? value : `${value.slice(0, maxChars)}...[truncated]`;
51
+ }
52
+
53
+ function normalizedError(error) {
54
+ if (error === undefined || error === null) return undefined;
55
+ if (error instanceof Error) {
56
+ return {
57
+ name: sanitizeExitText(error.name || error.constructor?.name || 'Error', 160),
58
+ message: sanitizeExitText(error.message || String(error), MAX_MESSAGE_CHARS),
59
+ stack: sanitizeExitText(error.stack || '', MAX_STACK_CHARS),
60
+ };
61
+ }
62
+ return {
63
+ name: typeof error === 'object' && typeof error.name === 'string'
64
+ ? sanitizeExitText(error.name, 160)
65
+ : 'NonErrorRejection',
66
+ message: sanitizeExitText(error, MAX_MESSAGE_CHARS),
67
+ };
68
+ }
69
+
70
+ function finiteInteger(value) {
71
+ return Number.isInteger(value) ? value : undefined;
72
+ }
73
+
74
+ function buildRuntimeExitRecord(input) {
75
+ const now = typeof input.now === 'function' ? input.now() : new Date();
76
+ const record = {
77
+ schemaVersion: 1,
78
+ ts: (now instanceof Date ? now : new Date(now)).toISOString(),
79
+ source: sanitizeExitText(input.source || 'unknown', 80),
80
+ kind: sanitizeExitText(input.kind || 'unknown', 120),
81
+ exitCode: finiteInteger(input.exitCode),
82
+ signal: input.signal ? sanitizeExitText(input.signal, 40) : undefined,
83
+ phase: input.phase ? sanitizeExitText(input.phase, 80) : undefined,
84
+ cliVersion: input.cliVersion ? sanitizeExitText(input.cliVersion, 80) : undefined,
85
+ profile: input.profile ? sanitizeExitText(input.profile, 120) : undefined,
86
+ sessionId: input.sessionId ? sanitizeExitText(input.sessionId, 240) : undefined,
87
+ pid: finiteInteger(input.pid ?? process.pid),
88
+ ppid: finiteInteger(input.ppid ?? process.ppid),
89
+ childPid: finiteInteger(input.childPid),
90
+ loaded: typeof input.loaded === 'boolean' ? input.loaded : undefined,
91
+ detail: input.detail ? sanitizeExitText(input.detail, MAX_DETAIL_CHARS) : undefined,
92
+ error: normalizedError(input.error),
93
+ };
94
+ return Object.fromEntries(Object.entries(record).filter(([, value]) => value !== undefined && value !== ''));
95
+ }
96
+
97
+ function trimLedger(filePath, maxBytes, retainBytes, incomingBytes) {
98
+ let size;
99
+ try {
100
+ size = fs.statSync(filePath).size;
101
+ } catch {
102
+ return;
103
+ }
104
+ if (size + incomingBytes <= maxBytes) return;
105
+ const contents = fs.readFileSync(filePath);
106
+ const keepFrom = Math.max(0, contents.length - Math.min(retainBytes, maxBytes - incomingBytes));
107
+ let lineStart = keepFrom;
108
+ if (lineStart > 0) {
109
+ const nextNewline = contents.indexOf(0x0a, lineStart);
110
+ lineStart = nextNewline === -1 ? contents.length : nextNewline + 1;
111
+ }
112
+ fs.writeFileSync(filePath, contents.subarray(lineStart), { mode: 0o600 });
113
+ }
114
+
115
+ function recordRuntimeExit(input = {}) {
116
+ try {
117
+ const homeDir = typeof input.homeDir === 'string' ? input.homeDir.trim() : '';
118
+ if (!homeDir) return false;
119
+ const filePath = runtimeExitLedgerPath(homeDir);
120
+ fs.mkdirSync(path.dirname(filePath), { recursive: true, mode: 0o700 });
121
+ const line = `${JSON.stringify(buildRuntimeExitRecord(input))}\n`;
122
+ const incomingBytes = Buffer.byteLength(line);
123
+ const maxBytes = Math.max(1024, finiteInteger(input.maxBytes) ?? DEFAULT_MAX_BYTES);
124
+ const retainBytes = Math.max(0, finiteInteger(input.retainBytes) ?? DEFAULT_RETAIN_BYTES);
125
+ trimLedger(filePath, maxBytes, retainBytes, incomingBytes);
126
+ fs.appendFileSync(filePath, line, { encoding: 'utf8', mode: 0o600 });
127
+ try {
128
+ fs.chmodSync(filePath, 0o600);
129
+ } catch {}
130
+ return true;
131
+ } catch {
132
+ return false;
133
+ }
134
+ }
135
+
136
+ module.exports = {
137
+ DEFAULT_MAX_BYTES,
138
+ DEFAULT_RETAIN_BYTES,
139
+ buildRuntimeExitRecord,
140
+ recordRuntimeExit,
141
+ runtimeExitLedgerPath,
142
+ sanitizeExitText,
143
+ };
package/blun.mjs CHANGED
@@ -8,6 +8,7 @@ import { createRequire } from "node:module";
8
8
  import telegramConsoleStatusPolicy from "./bin/telegram-console-status-policy.cjs";
9
9
  import cognitiveGoalAutostartPolicy from "./bin/cognitive-goal-autostart-policy.cjs";
10
10
  import cognitiveGoalTimeTriggerController from "./bin/cognitive-goal-time-trigger-controller.cjs";
11
+ import runtimeExitLedger from "./bin/runtime-exit-ledger.cjs";
11
12
  import crypto$1, { createHash, randomBytes, randomInt, randomUUID, timingSafeEqual } from "node:crypto";
12
13
  import * as fs$16 from "node:fs";
13
14
  import Kt, { accessSync, appendFileSync, chmodSync, closeSync, constants, copyFileSync, createReadStream, createWriteStream, existsSync, fsyncSync, linkSync, lstatSync, mkdirSync, openSync, promises, readFileSync, readSync, readdirSync, realpathSync, renameSync, rmSync, statSync, unlinkSync, writeFileSync, writeSync } from "node:fs";
@@ -40,6 +41,7 @@ import { pipeline as pipeline$1 } from "node:stream/promises";
40
41
  const { writeTelegramConsoleStatus } = telegramConsoleStatusPolicy;
41
42
  const { goalAutostartDecision, goalContinuationDecision } = cognitiveGoalAutostartPolicy;
42
43
  const { GoalTimeTriggerController } = cognitiveGoalTimeTriggerController;
44
+ const { recordRuntimeExit } = runtimeExitLedger;
43
45
  import { EventEmitter as EventEmitter$1 } from "node:events";
44
46
  import { StringDecoder } from "node:string_decoder";
45
47
  import co from "node:assert";
@@ -516808,6 +516810,17 @@ var BlunTUI = class {
516808
516810
  if (process.platform !== "win32") signals.push("SIGHUP");
516809
516811
  for (const signal of signals) {
516810
516812
  const handler = () => {
516813
+ recordRuntimeExit({
516814
+ homeDir: process.env["BLUN_HOME"],
516815
+ source: "core",
516816
+ kind: "signal",
516817
+ exitCode: signal === "SIGTERM" ? 143 : 129,
516818
+ signal,
516819
+ phase: "runtime",
516820
+ cliVersion: process.env["BLUN_PUBLIC_PACKAGE_VERSION"],
516821
+ profile: process.env["BLUN_PROFILE"],
516822
+ sessionId: this.getCurrentSessionId()
516823
+ });
516811
516824
  if (signal === "SIGHUP") {
516812
516825
  this.emergencyTerminalExit();
516813
516826
  return;
@@ -516824,7 +516837,20 @@ var BlunTUI = class {
516824
516837
  });
516825
516838
  }
516826
516839
  const terminalErrorHandler = (error) => {
516827
- if (isDeadTerminalError(error)) this.emergencyTerminalExit();
516840
+ if (isDeadTerminalError(error)) {
516841
+ recordRuntimeExit({
516842
+ homeDir: process.env["BLUN_HOME"],
516843
+ source: "core",
516844
+ kind: "dead-terminal",
516845
+ exitCode: 129,
516846
+ phase: "runtime",
516847
+ cliVersion: process.env["BLUN_PUBLIC_PACKAGE_VERSION"],
516848
+ profile: process.env["BLUN_PROFILE"],
516849
+ sessionId: this.getCurrentSessionId(),
516850
+ error
516851
+ });
516852
+ this.emergencyTerminalExit();
516853
+ }
516828
516854
  };
516829
516855
  process.stdout.on("error", terminalErrorHandler);
516830
516856
  process.stderr.on("error", terminalErrorHandler);
@@ -519588,6 +519614,15 @@ async function runShell(opts, version) {
519588
519614
  const trackLifecycle = (event, properties) => {
519589
519615
  trackLifecycleForSession(tui.getCurrentSessionId(), event, properties);
519590
519616
  };
519617
+ const recordShellExit = (entry) => recordRuntimeExit({
519618
+ homeDir: process.env["BLUN_HOME"] ?? telemetryBootstrap.homeDir,
519619
+ source: "core",
519620
+ phase: "runtime",
519621
+ cliVersion: version,
519622
+ profile: process.env["BLUN_PROFILE"],
519623
+ sessionId: tui.getCurrentSessionId(),
519624
+ ...entry
519625
+ });
519591
519626
  let savedStty;
519592
519627
  try {
519593
519628
  const saved = execSync("stty -g", {
@@ -519621,12 +519656,22 @@ async function runShell(opts, version) {
519621
519656
  process.exit(exitCode);
519622
519657
  };
519623
519658
  const onUncaughtException = (error) => {
519659
+ recordShellExit({
519660
+ kind: "uncaught-exception",
519661
+ exitCode: 1,
519662
+ error
519663
+ });
519624
519664
  try {
519625
519665
  log.error("uncaughtException, restoring terminal and exiting", { error: String(error) });
519626
519666
  } catch {}
519627
519667
  emergencyExit(1);
519628
519668
  };
519629
519669
  const onUnhandledRejection = (reason) => {
519670
+ recordShellExit({
519671
+ kind: "unhandled-rejection",
519672
+ exitCode: 1,
519673
+ error: reason
519674
+ });
519630
519675
  try {
519631
519676
  log.error("unhandledRejection, restoring terminal and exiting", { reason: String(reason) });
519632
519677
  } catch {}
@@ -519641,6 +519686,10 @@ async function runShell(opts, version) {
519641
519686
  tui.onExit = async (exitCode = 0) => {
519642
519687
  const sessionId = tui.getCurrentSessionId();
519643
519688
  const hasContent = tui.hasSessionContent();
519689
+ recordShellExit({
519690
+ kind: "normal-exit",
519691
+ exitCode
519692
+ });
519644
519693
  setCrashPhase("shutdown");
519645
519694
  trackLifecycle("exit", { duration_ms: Date.now() - startedAt });
519646
519695
  await shutdownTelemetry({ timeoutMs: CLI_SHUTDOWN_TIMEOUT_MS });
@@ -519671,6 +519720,12 @@ async function runShell(opts, version) {
519671
519720
  ...tui.getStartupPhaseMs()
519672
519721
  });
519673
519722
  } catch (error) {
519723
+ recordShellExit({
519724
+ kind: "startup-failure",
519725
+ exitCode: 1,
519726
+ phase: "startup",
519727
+ error
519728
+ });
519674
519729
  removeCrashHandlers();
519675
519730
  setCrashPhase("shutdown");
519676
519731
  trackLifecycle("exit", { duration_ms: Date.now() - startedAt });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blun-king-cli",
3
- "version": "9.1.422",
3
+ "version": "9.1.424",
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": {