blun-king-cli 9.1.23 → 9.1.24

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/bin/blun.js CHANGED
@@ -313,9 +313,9 @@ function connectRuntimeBroker(endpoint) {
313
313
  });
314
314
  }
315
315
 
316
- async function connectExistingRuntimeLease() {
316
+ async function connectExistingRuntimeLease(timeoutMs = 3_000) {
317
317
  const endpoint = deriveLeaseEndpoint('runtime');
318
- const deadline = Date.now() + 3_000;
318
+ const deadline = Date.now() + timeoutMs;
319
319
  while (Date.now() < deadline) {
320
320
  const connected = await connectRuntimeBroker(endpoint);
321
321
  if (connected.acquired) return connected;
@@ -328,18 +328,17 @@ async function main() {
328
328
  const explicitUpdateRequest = process.argv.length === 3
329
329
  && /^(?:update|upgrade)$/iu.test(process.argv[2] || '');
330
330
  let noticeResult = await acquireNoticeLease();
331
- if (!noticeResult.acquired && explicitUpdateRequest) {
332
- process.stderr.write('BLUN laeuft noch. Beende die laufenden Konsolen und starte das Update erneut.\n');
333
- process.exitCode = 1;
334
- return;
335
- }
336
331
  let runtimeResult;
337
332
  try {
338
333
  if (explicitUpdateRequest) {
339
- if (!await installLeaseIsClear()) {
340
- process.stderr.write('BLUN laeuft noch. Beende die laufenden Konsolen und starte das Update erneut.\n');
341
- process.exitCode = 1;
342
- return;
334
+ runtimeResult = await connectExistingRuntimeLease(750);
335
+ if (!runtimeResult.acquired) {
336
+ runtimeResult = undefined;
337
+ if (!noticeResult.acquired || !await installLeaseIsClear()) {
338
+ process.stderr.write('Der geschuetzte BLUN-Start ist gerade nicht verfuegbar.\n');
339
+ process.exitCode = 1;
340
+ return;
341
+ }
343
342
  }
344
343
  } else {
345
344
  const installClear = await installLeaseIsClear();
@@ -373,7 +372,7 @@ async function main() {
373
372
  mode: filename.startsWith('king') ? 'king' : 'blun',
374
373
  noticeLease: noticeResult.lease,
375
374
  runtimeLease: runtimeResult?.lease,
376
- skipUpdateNotice: runtimeResult?.acquired === true,
375
+ skipUpdateNotice: runtimeResult?.acquired === true || !noticeResult.acquired,
377
376
  });
378
377
  } finally {
379
378
  await runtimeResult?.lease?.release();
package/bin/king.js CHANGED
@@ -313,9 +313,9 @@ function connectRuntimeBroker(endpoint) {
313
313
  });
314
314
  }
315
315
 
316
- async function connectExistingRuntimeLease() {
316
+ async function connectExistingRuntimeLease(timeoutMs = 3_000) {
317
317
  const endpoint = deriveLeaseEndpoint('runtime');
318
- const deadline = Date.now() + 3_000;
318
+ const deadline = Date.now() + timeoutMs;
319
319
  while (Date.now() < deadline) {
320
320
  const connected = await connectRuntimeBroker(endpoint);
321
321
  if (connected.acquired) return connected;
@@ -328,18 +328,17 @@ async function main() {
328
328
  const explicitUpdateRequest = process.argv.length === 3
329
329
  && /^(?:update|upgrade)$/iu.test(process.argv[2] || '');
330
330
  let noticeResult = await acquireNoticeLease();
331
- if (!noticeResult.acquired && explicitUpdateRequest) {
332
- process.stderr.write('BLUN laeuft noch. Beende die laufenden Konsolen und starte das Update erneut.\n');
333
- process.exitCode = 1;
334
- return;
335
- }
336
331
  let runtimeResult;
337
332
  try {
338
333
  if (explicitUpdateRequest) {
339
- if (!await installLeaseIsClear()) {
340
- process.stderr.write('BLUN laeuft noch. Beende die laufenden Konsolen und starte das Update erneut.\n');
341
- process.exitCode = 1;
342
- return;
334
+ runtimeResult = await connectExistingRuntimeLease(750);
335
+ if (!runtimeResult.acquired) {
336
+ runtimeResult = undefined;
337
+ if (!noticeResult.acquired || !await installLeaseIsClear()) {
338
+ process.stderr.write('Der geschuetzte BLUN-Start ist gerade nicht verfuegbar.\n');
339
+ process.exitCode = 1;
340
+ return;
341
+ }
343
342
  }
344
343
  } else {
345
344
  const installClear = await installLeaseIsClear();
@@ -373,7 +372,7 @@ async function main() {
373
372
  mode: filename.startsWith('king') ? 'king' : 'blun',
374
373
  noticeLease: noticeResult.lease,
375
374
  runtimeLease: runtimeResult?.lease,
376
- skipUpdateNotice: runtimeResult?.acquired === true,
375
+ skipUpdateNotice: runtimeResult?.acquired === true || !noticeResult.acquired,
377
376
  });
378
377
  } finally {
379
378
  await runtimeResult?.lease?.release();
@@ -210,7 +210,7 @@ async function runLauncher(options = {}) {
210
210
  };
211
211
 
212
212
  try {
213
- if (!runtimeLease) {
213
+ if (!runtimeLease && !explicitUpdateRequest) {
214
214
  const installProbe = await tryAcquireUpdateLease({ packageRoot: PKG, role: 'install' });
215
215
  if (!installProbe.acquired) {
216
216
  process.stderr.write(explicitUpdateRequest
@@ -24,6 +24,7 @@ const SNOOZE_MS = 7 * 24 * 60 * 60 * 1000;
24
24
  const REQUEST_TIMEOUT_MS = 2_500;
25
25
  const STATE_FILE = 'update-notice.json';
26
26
  const UPDATE_SKIP_FILE = 'update-skip-version.json';
27
+ const PENDING_UPDATE_FILE = 'update-pending.json';
27
28
  const UPDATE_DIRECTORY = 'updates';
28
29
  const UPDATE_KEY_ESCAPE_TIMEOUT_MS = 500;
29
30
  const UPDATE_SIGNAL_EXIT_CODES = Object.freeze({
@@ -441,6 +442,59 @@ function writeSkippedVersion(blunDir, skippedVersion) {
441
442
  }
442
443
  }
443
444
 
445
+ function pendingUpdatePath(blunDir) {
446
+ return path.join(blunDir, PENDING_UPDATE_FILE);
447
+ }
448
+
449
+ function readPendingUpdate(blunDir) {
450
+ try {
451
+ const filePath = pendingUpdatePath(blunDir);
452
+ if (!fs.existsSync(filePath)) return undefined;
453
+ securePrivateFile(filePath);
454
+ const stat = fs.lstatSync(filePath);
455
+ if (!stat.isFile() || stat.isSymbolicLink() || stat.size > 1_024) return undefined;
456
+ const record = exactDataRecord(JSON.parse(fs.readFileSync(filePath, 'utf8')), [
457
+ 'targetVersion',
458
+ 'version',
459
+ ]);
460
+ if (!record || record.version !== 1 || !parseSemver(record.targetVersion)) return undefined;
461
+ return record.targetVersion;
462
+ } catch {
463
+ return undefined;
464
+ }
465
+ }
466
+
467
+ function writePendingUpdate(blunDir, targetVersion) {
468
+ if (!parseSemver(targetVersion)) throw new Error('INVALID_UPDATE_VERSION');
469
+ ensurePrivateDirectory(blunDir);
470
+ const filePath = pendingUpdatePath(blunDir);
471
+ const temporaryPath = path.join(
472
+ blunDir,
473
+ `.${PENDING_UPDATE_FILE}.${process.pid}.${randomUUID()}.tmp`,
474
+ );
475
+ try {
476
+ writePrivateFile(temporaryPath, `${JSON.stringify({
477
+ version: 1,
478
+ targetVersion,
479
+ })}\n`);
480
+ const handle = fs.openSync(temporaryPath, 'r+');
481
+ try {
482
+ fs.fsyncSync(handle);
483
+ } finally {
484
+ fs.closeSync(handle);
485
+ }
486
+ fs.renameSync(temporaryPath, filePath);
487
+ securePrivateFile(filePath);
488
+ } catch (error) {
489
+ fs.rmSync(temporaryPath, { force: true });
490
+ throw error;
491
+ }
492
+ }
493
+
494
+ function clearPendingUpdate(blunDir) {
495
+ fs.rmSync(pendingUpdatePath(blunDir), { force: true });
496
+ }
497
+
444
498
  function isInteractiveUpdateStart({ argv, env, stdin, stdout }) {
445
499
  const isOrdinaryStartup = Array.isArray(argv)
446
500
  && (argv.length === 0
@@ -1145,6 +1199,19 @@ function explicitFailure(stderr, reason, exitCode) {
1145
1199
  };
1146
1200
  }
1147
1201
 
1202
+ function queueExplicitUpdate(blunDir, release, stdout, stderr) {
1203
+ try {
1204
+ ensurePrivateDirectory(blunDir);
1205
+ writePendingUpdate(blunDir, release.version);
1206
+ } catch {
1207
+ return explicitFailure(stderr, 'private_home_unavailable');
1208
+ }
1209
+ stdout.write(
1210
+ `Update ${release.version} vorgemerkt. Es wird beim ersten freien Start automatisch installiert.\n`,
1211
+ );
1212
+ return { kind: 'queued', version: release.version };
1213
+ }
1214
+
1148
1215
  async function runUpdateFlow(options, explicitUpdate) {
1149
1216
  const {
1150
1217
  currentVersion,
@@ -1164,8 +1231,13 @@ async function runUpdateFlow(options, explicitUpdate) {
1164
1231
  : { kind: 'continue', reason: 'invalid_current_version' };
1165
1232
  }
1166
1233
 
1234
+ let pendingVersion = readPendingUpdate(blunDir);
1235
+ if (pendingVersion !== undefined && compareSemver(currentVersion, pendingVersion) >= 0) {
1236
+ clearPendingUpdate(blunDir);
1237
+ pendingVersion = undefined;
1238
+ }
1167
1239
  const now = (options.now || Date.now)();
1168
- if (!explicitUpdate && readSnoozeState(blunDir, now)) {
1240
+ if (!explicitUpdate && pendingVersion === undefined && readSnoozeState(blunDir, now)) {
1169
1241
  return { kind: 'continue', reason: 'snoozed' };
1170
1242
  }
1171
1243
 
@@ -1198,20 +1270,11 @@ async function runUpdateFlow(options, explicitUpdate) {
1198
1270
  let installerSession;
1199
1271
 
1200
1272
  try {
1201
- if (!explicitUpdate && readSnoozeState(blunDir, (options.now || Date.now)())) {
1273
+ if (!explicitUpdate
1274
+ && pendingVersion === undefined
1275
+ && readSnoozeState(blunDir, (options.now || Date.now)())) {
1202
1276
  return { kind: 'continue', reason: 'snoozed' };
1203
1277
  }
1204
- const initialInstallLease = await probeInstallLease(options, packageRoot);
1205
- if (initialInstallLease === 'busy') {
1206
- return explicitUpdate
1207
- ? explicitFailure(stderr, 'update_in_progress')
1208
- : { kind: 'update_in_progress' };
1209
- }
1210
- if (initialInstallLease !== 'clear') {
1211
- return explicitUpdate
1212
- ? explicitFailure(stderr, 'lease_unavailable')
1213
- : { kind: 'continue', reason: 'lease_unavailable' };
1214
- }
1215
1278
  let sources;
1216
1279
  try {
1217
1280
  sources = await (options.loadReleaseSources || loadReleaseSources)();
@@ -1229,10 +1292,22 @@ async function runUpdateFlow(options, explicitUpdate) {
1229
1292
  ? explicitNoTarget(stdout, reason)
1230
1293
  : { kind: 'continue', reason };
1231
1294
  }
1232
- if (!explicitUpdate && readSkippedVersion(blunDir) === release.version) {
1295
+ const queuedForRelease = pendingVersion === release.version;
1296
+ const initialInstallLease = await probeInstallLease(options, packageRoot);
1297
+ if (initialInstallLease === 'busy') {
1298
+ return explicitUpdate
1299
+ ? queueExplicitUpdate(blunDir, release, stdout, stderr)
1300
+ : { kind: 'update_in_progress' };
1301
+ }
1302
+ if (initialInstallLease !== 'clear') {
1303
+ return explicitUpdate
1304
+ ? explicitFailure(stderr, 'lease_unavailable')
1305
+ : { kind: 'continue', reason: 'lease_unavailable' };
1306
+ }
1307
+ if (!explicitUpdate && !queuedForRelease && readSkippedVersion(blunDir) === release.version) {
1233
1308
  return { kind: 'continue', reason: 'skipped_version' };
1234
1309
  }
1235
- if (explicitUpdate) {
1310
+ if (explicitUpdate || queuedForRelease) {
1236
1311
  try {
1237
1312
  ensurePrivateDirectory(blunDir);
1238
1313
  } catch {
@@ -1249,7 +1324,7 @@ async function runUpdateFlow(options, explicitUpdate) {
1249
1324
  const prePromptInstallLease = await probeInstallLease(options, packageRoot);
1250
1325
  if (prePromptInstallLease === 'busy') {
1251
1326
  return explicitUpdate
1252
- ? explicitFailure(stderr, 'update_in_progress')
1327
+ ? queueExplicitUpdate(blunDir, release, stdout, stderr)
1253
1328
  : { kind: 'update_in_progress' };
1254
1329
  }
1255
1330
  if (prePromptInstallLease !== 'clear') {
@@ -1272,7 +1347,7 @@ async function runUpdateFlow(options, explicitUpdate) {
1272
1347
  }
1273
1348
  }
1274
1349
  let action;
1275
- if (explicitUpdate) {
1350
+ if (explicitUpdate || queuedForRelease) {
1276
1351
  action = 'install';
1277
1352
  } else {
1278
1353
  try {
@@ -1337,6 +1412,7 @@ async function runUpdateFlow(options, explicitUpdate) {
1337
1412
  }
1338
1413
  if (installResult?.code === 0 && installResult?.verified === true) {
1339
1414
  updateProgress(4, 'Installation geprüft');
1415
+ clearPendingUpdate(blunDir);
1340
1416
  stdout.write('Update erfolgreich installiert. Starte BLUN Code neu, um die neue Version zu verwenden.\n');
1341
1417
  return { kind: 'installed', version: release.version };
1342
1418
  }
package/blun.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- // BLUN_BUILD_INPUT_SHA256:ceb20a9e0d1997d41a1341bbfc3961f3f60de3ed6867c9cff4ee45bc88198aab
2
+ // BLUN_BUILD_INPUT_SHA256:420510ac14726514cc4725577c2fe8d0fd038c1471bdbda1c15ace32f3ceaba0
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);
@@ -261328,6 +261328,10 @@ var init_blun_media$1 = __esmMin((() => {
261328
261328
  isError: terminalError
261329
261329
  };
261330
261330
  }
261331
+ if (result.kind === "text") return {
261332
+ output: result.text,
261333
+ isError: false
261334
+ };
261331
261335
  const url = `data:${result.mimeType};base64,${Buffer.from(result.data).toString("base64")}`;
261332
261336
  const mediaPart = contentPartFor(result.mimeType, url);
261333
261337
  if (mediaPart === void 0) return {
@@ -309372,6 +309376,12 @@ var init_blun_media = __esmMin((() => {
309372
309376
  await assertSuccess(response, "Media lookup");
309373
309377
  const mimeType = response.headers.get("content-type")?.split(";", 1)[0]?.trim().toLowerCase() ?? "";
309374
309378
  if (mimeType === "application/json" || mimeType.endsWith("+json")) return parseStatus(await response.json(), id);
309379
+ if (mimeType === "text/plain") return {
309380
+ kind: "text",
309381
+ id,
309382
+ mimeType,
309383
+ text: await response.text()
309384
+ };
309375
309385
  if (!mimeType.startsWith("image/") && !mimeType.startsWith("audio/") && !mimeType.startsWith("video/")) throw new Error(`Media lookup returned unsupported content type ${mimeType || "(missing)"}`);
309376
309386
  return {
309377
309387
  kind: "file",
@@ -340476,6 +340486,18 @@ const PERSONAL_MEMORY_MEMORY_CREATE_METHOD = "POST";
340476
340486
  const PERSONAL_MEMORY_RECALL_METHOD = "POST";
340477
340487
  const PERSONAL_MEMORY_EXPLICIT_KEY = "explicit_user_memory";
340478
340488
  const PERSONAL_MEMORY_EXPLICIT_CATEGORY = "user_explicit";
340489
+ const SAFE_BROKER_ERROR_CODES = new Set([
340490
+ "AUTHENTICATION_REQUIRED",
340491
+ "EXPLICIT_CONSENT_REQUIRED",
340492
+ "INTERNAL_ERROR",
340493
+ "INTERACTIVE_CONFIRMATION_REQUIRED",
340494
+ "INSUFFICIENT_SCOPE",
340495
+ "INVALID_PAYLOAD",
340496
+ "MEMORY_NOT_FOUND",
340497
+ "MEMORY_WRITE_DISABLED",
340498
+ "SECRET_REJECTED",
340499
+ "UNAVAILABLE"
340500
+ ]);
340479
340501
  var PersonalMemoryBrokerError = class extends Error {
340480
340502
  code;
340481
340503
  status;
@@ -340513,9 +340535,118 @@ function explicitPersonalMemoryBody(content) {
340513
340535
  kategorie: PERSONAL_MEMORY_EXPLICIT_CATEGORY
340514
340536
  };
340515
340537
  }
340538
+ var PersonalMemoryBrokerClient = class {
340539
+ options;
340540
+ baseUrl;
340541
+ fetchImpl;
340542
+ idempotencyKey;
340543
+ timeoutMs;
340544
+ constructor(options) {
340545
+ this.options = options;
340546
+ this.baseUrl = options.baseUrl.replace(/\/+$/, "");
340547
+ this.fetchImpl = options.fetchImpl ?? fetch;
340548
+ this.idempotencyKey = options.idempotencyKey ?? randomUUID;
340549
+ this.timeoutMs = options.timeoutMs ?? 1e4;
340550
+ }
340551
+ async getSettings() {
340552
+ const parsed = parsePersonalMemorySettings(await this.request("GET", PERSONAL_MEMORY_SETTINGS_PATH));
340553
+ if (parsed === void 0) throw new PersonalMemoryBrokerError("INVALID_PAYLOAD");
340554
+ return parsed;
340555
+ }
340556
+ async updateSettings(patch) {
340557
+ await this.request("PUT", PERSONAL_MEMORY_SETTINGS_PATH, patch);
340558
+ }
340559
+ async request(method, path, body) {
340560
+ if (!isAllowedBrokerRequest(method, path)) throw new PersonalMemoryBrokerError("INVALID_PAYLOAD");
340561
+ const idempotencyKey = method === "GET" ? void 0 : this.idempotencyKey();
340562
+ const firstToken = await this.accessToken();
340563
+ let response = await this.fetchWithToken(method, path, firstToken, body, idempotencyKey);
340564
+ if (response.status === 401) {
340565
+ const refreshed = await this.accessToken({ force: true });
340566
+ response = await this.fetchWithToken(method, path, refreshed, body, idempotencyKey);
340567
+ }
340568
+ if (!response.ok) throw await brokerHttpError(response);
340569
+ if (response.status === 204) return null;
340570
+ const text = await response.text();
340571
+ if (text.trim().length === 0) return null;
340572
+ try {
340573
+ return JSON.parse(text);
340574
+ } catch {
340575
+ throw new PersonalMemoryBrokerError("INVALID_PAYLOAD");
340576
+ }
340577
+ }
340578
+ async accessToken(options) {
340579
+ try {
340580
+ const token = await this.options.tokenProvider.getAccessToken(options);
340581
+ if (token.trim().length === 0) throw new Error("empty token");
340582
+ return token;
340583
+ } catch {
340584
+ throw new PersonalMemoryBrokerError("AUTHENTICATION_REQUIRED");
340585
+ }
340586
+ }
340587
+ async fetchWithToken(method, path, accessToken, body, idempotencyKey) {
340588
+ const controller = new AbortController();
340589
+ const timer = setTimeout(() => {
340590
+ controller.abort();
340591
+ }, this.timeoutMs);
340592
+ timer.unref?.();
340593
+ const headers = new Headers();
340594
+ headers.set("Accept", "application/json");
340595
+ headers.set("Authorization", `Bearer ${accessToken}`);
340596
+ if (body !== void 0) headers.set("Content-Type", "application/json");
340597
+ if (idempotencyKey !== void 0) headers.set("Idempotency-Key", idempotencyKey);
340598
+ try {
340599
+ return await this.fetchImpl(`${this.baseUrl}${path}`, {
340600
+ method,
340601
+ headers,
340602
+ body: body === void 0 ? void 0 : JSON.stringify(body),
340603
+ signal: controller.signal
340604
+ });
340605
+ } catch {
340606
+ throw new PersonalMemoryBrokerError("UNAVAILABLE");
340607
+ } finally {
340608
+ clearTimeout(timer);
340609
+ }
340610
+ }
340611
+ };
340612
+ async function brokerHttpError(response) {
340613
+ const status = response.status;
340614
+ const responseCode = await safeBrokerResponseCode(response);
340615
+ if (responseCode !== void 0) return new PersonalMemoryBrokerError(responseCode, status);
340616
+ if (status === 401) return new PersonalMemoryBrokerError("AUTHENTICATION_REQUIRED", status);
340617
+ if (status === 403) return new PersonalMemoryBrokerError("INSUFFICIENT_SCOPE", status);
340618
+ return new PersonalMemoryBrokerError("UNAVAILABLE", status);
340619
+ }
340620
+ async function safeBrokerResponseCode(response) {
340621
+ try {
340622
+ const payload = await response.json();
340623
+ if (!isRecord$11(payload)) return void 0;
340624
+ const nested = isRecord$11(payload["error"]) ? payload["error"]["code"] : void 0;
340625
+ const candidate = payload["code"] ?? payload["error_code"] ?? nested;
340626
+ return typeof candidate === "string" && SAFE_BROKER_ERROR_CODES.has(candidate) ? candidate : void 0;
340627
+ } catch {
340628
+ return;
340629
+ }
340630
+ }
340516
340631
  function isRecord$11(value) {
340517
340632
  return typeof value === "object" && value !== null && !Array.isArray(value);
340518
340633
  }
340634
+ function isAllowedBrokerRequest(method, path) {
340635
+ let url;
340636
+ try {
340637
+ url = new URL(path, "https://personal-memory.invalid");
340638
+ } catch {
340639
+ return false;
340640
+ }
340641
+ if (url.origin !== "https://personal-memory.invalid") return false;
340642
+ if (url.search.length > 0 || url.hash.length > 0) return false;
340643
+ const pathname = url.pathname;
340644
+ if (pathname === PERSONAL_MEMORY_SETTINGS_PATH) return method === "GET" || method === "PUT";
340645
+ if (pathname === PERSONAL_MEMORY_MEMORIES_PATH) return method === "GET" || method === "POST";
340646
+ if (/^\/v1\/me\/memories\/[0-9a-f-]{36}$/i.test(pathname)) return false;
340647
+ if (pathname === PERSONAL_MEMORY_RECALL_PATH) return method === PERSONAL_MEMORY_RECALL_METHOD;
340648
+ return false;
340649
+ }
340519
340650
  //#endregion
340520
340651
  //#region src/personal-memory/explicit-remember.ts
340521
340652
  /** The sole Phase-1 write path: invoked by an explicit host command, never by the model. */
@@ -340539,7 +340670,17 @@ async function rememberExplicitPersonalMemory(options) {
340539
340670
  //#region src/personal-memory/runtime.ts
340540
340671
  init_src$3();
340541
340672
  function createPersonalMemoryBrokerClient(harness) {
340542
- throw new PersonalMemoryBrokerError("UNAVAILABLE");
340673
+ const provider = loadRuntimeConfigSafe(harness.configPath).config.providers[DEFAULT_OAUTH_PROVIDER_NAME];
340674
+ const runtime = resolveBlunCodeRuntimeAuth({
340675
+ configuredBaseUrl: provider?.baseUrl,
340676
+ configuredOAuthRef: provider?.oauth
340677
+ });
340678
+ const brokerBaseUrl = runtime.oauthRef.oauthHost ?? BLUN_FLOW_CONFIG.oauthHost;
340679
+ if (brokerBaseUrl.trim().length === 0) throw new PersonalMemoryBrokerError("UNAVAILABLE");
340680
+ return new PersonalMemoryBrokerClient({
340681
+ baseUrl: brokerBaseUrl,
340682
+ tokenProvider: harness.auth.resolveOAuthTokenProvider(DEFAULT_OAUTH_PROVIDER_NAME, runtime.oauthRef)
340683
+ });
340543
340684
  }
340544
340685
  //#endregion
340545
340686
  //#region src/cli/sub/personal-memory.ts
@@ -399120,42 +399261,42 @@ registerUiCatalogFragment({
399120
399261
  registerUiCatalogFragment({
399121
399262
  en: {
399122
399263
  "startupPersonalMemory.title": "Personal memory",
399123
- "startupPersonalMemory.details": "When personal memory is enabled, BLUN automatically extracts useful information from your chats and can use it in future conversations. This stays off until you enable it.",
399264
+ "startupPersonalMemory.details": "When personal memory is enabled, BLUN can save information only when you explicitly ask it to remember something and can use saved information in later conversations. Your chats are not evaluated automatically. This stays off until you enable it.",
399124
399265
  "startupPersonalMemory.enable": "Enable memory",
399125
399266
  "startupPersonalMemory.decline": "Do not enable",
399126
399267
  "startupPersonalMemory.persistenceFailed": "Memory settings could not be saved. Personal memory remains off."
399127
399268
  },
399128
399269
  de: {
399129
399270
  "startupPersonalMemory.title": "Persönliches Gedächtnis",
399130
- "startupPersonalMemory.details": "Wenn das persönliche Gedächtnis aktiviert ist, übernimmt BLUN automatisch nützliche Informationen aus deinen Chats und kann sie in späteren Gesprächen verwenden. Die Funktion bleibt deaktiviert, bis du sie einschaltest.",
399271
+ "startupPersonalMemory.details": "Wenn das persönliche Gedächtnis aktiviert ist, kann BLUN Informationen nur dann speichern, wenn du ausdrücklich darum bittest, sich etwas zu merken. Gespeicherte Informationen können in späteren Gesprächen verwendet werden. Deine Chats werden nicht automatisch ausgewertet. Die Funktion bleibt deaktiviert, bis du sie einschaltest.",
399131
399272
  "startupPersonalMemory.enable": "Gedächtnis aktivieren",
399132
399273
  "startupPersonalMemory.decline": "Nicht aktivieren",
399133
399274
  "startupPersonalMemory.persistenceFailed": "Die Gedächtniseinstellungen konnten nicht gespeichert werden. Das persönliche Gedächtnis bleibt deaktiviert."
399134
399275
  },
399135
399276
  es: {
399136
399277
  "startupPersonalMemory.title": "Memoria personal",
399137
- "startupPersonalMemory.details": "Cuando activas la memoria personal, BLUN extrae automáticamente información útil de tus chats y puede utilizarla en conversaciones futuras. La función permanece desactivada hasta que la actives.",
399278
+ "startupPersonalMemory.details": "Cuando la memoria personal está activada, BLUN solo puede guardar información si le pides expresamente que recuerde algo y puede utilizar la información guardada en conversaciones posteriores. Tus chats no se analizan automáticamente. Esta función permanece desactivada hasta que la actives.",
399138
399279
  "startupPersonalMemory.enable": "Activar la memoria",
399139
399280
  "startupPersonalMemory.decline": "No activar",
399140
399281
  "startupPersonalMemory.persistenceFailed": "No se pudo guardar la configuración de la memoria. La memoria personal permanece desactivada."
399141
399282
  },
399142
399283
  fr: {
399143
399284
  "startupPersonalMemory.title": "Mémoire personnelle",
399144
- "startupPersonalMemory.details": "Lorsque vous activez la mémoire personnelle, BLUN extrait automatiquement les informations utiles de vos conversations et peut les réutiliser ultérieurement. La fonction reste désactivée tant que vous ne l’activez pas.",
399285
+ "startupPersonalMemory.details": "Lorsque la mémoire personnelle est activée, BLUN ne peut enregistrer des informations que si vous lui demandez explicitement de retenir quelque chose. Il peut ensuite utiliser les informations enregistrées dans de futures conversations. Vos discussions ne sont pas analysées automatiquement. Cette fonction reste désactivée tant que vous ne l’activez pas.",
399145
399286
  "startupPersonalMemory.enable": "Activer la mémoire",
399146
399287
  "startupPersonalMemory.decline": "Ne pas activer",
399147
399288
  "startupPersonalMemory.persistenceFailed": "Les réglages de la mémoire n’ont pas pu être enregistrés. La mémoire personnelle reste désactivée."
399148
399289
  },
399149
399290
  sv: {
399150
399291
  "startupPersonalMemory.title": "Personligt minne",
399151
- "startupPersonalMemory.details": "När du aktiverar det personliga minnet hämtar BLUN automatiskt användbar information från dina chattar och kan använda den i framtida samtal. Funktionen är avstängd tills du aktiverar den.",
399292
+ "startupPersonalMemory.details": "När det personliga minnet är aktiverat kan BLUN bara spara information om du uttryckligen ber BLUN att komma ihåg något. Sparad information kan sedan användas i framtida samtal. Dina chattar analyseras inte automatiskt. Funktionen är avstängd tills du aktiverar den.",
399152
399293
  "startupPersonalMemory.enable": "Aktivera minnet",
399153
399294
  "startupPersonalMemory.decline": "Aktivera inte",
399154
399295
  "startupPersonalMemory.persistenceFailed": "Minnesinställningarna kunde inte sparas. Det personliga minnet förblir avstängt."
399155
399296
  },
399156
399297
  cs: {
399157
399298
  "startupPersonalMemory.title": "Osobní paměť",
399158
- "startupPersonalMemory.details": "Když zapnete osobní paměť, BLUN automaticky získává užitečné informace z vašich chatů a může je použít v budoucích konverzacích. Funkce zůstává vypnutá, dokud ji nezapnete.",
399299
+ "startupPersonalMemory.details": "Když je osobní paměť zapnutá, BLUN může uložit informace pouze tehdy, když ho výslovně požádáte, aby si něco zapamatoval. Uložené informace pak může použít v budoucích konverzacích. Vaše chaty se automaticky nevyhodnocují. Funkce zůstává vypnutá, dokud ji nezapnete.",
399159
399300
  "startupPersonalMemory.enable": "Zapnout paměť",
399160
399301
  "startupPersonalMemory.decline": "Nezapínat",
399161
399302
  "startupPersonalMemory.persistenceFailed": "Nastavení paměti se nepodařilo uložit. Osobní paměť zůstává vypnutá."
@@ -509168,11 +509309,9 @@ var BlunTUI = class {
509168
509309
  editorKeyboard;
509169
509310
  scrollbackController;
509170
509311
  personalMemoryController;
509171
- accountMemory;
509172
- accountMemoryEnabledForSession = false;
509173
- pendingAccountMemoryExtractions = /* @__PURE__ */ new Set();
509174
509312
  managedQuotaWarningController;
509175
509313
  managedQuotaWarningPersistence = Promise.resolve();
509314
+ footerMounted = false;
509176
509315
  /** Timer that auto-clears the one-shot "moved to background" footer hint. */
509177
509316
  detachHintClearTimer;
509178
509317
  telegramChannel;
@@ -509188,7 +509327,6 @@ var BlunTUI = class {
509188
509327
  }
509189
509328
  constructor(harness, startupInput) {
509190
509329
  this.harness = harness;
509191
- this.accountMemory = createAccountMemoryClient(harness.auth);
509192
509330
  const initialAppState = createInitialAppState(startupInput);
509193
509331
  setCurrentUiLocale(initialAppState.uiLocale);
509194
509332
  const invalidAppearance = initialAppState.appearance !== void 0 && !validateAppearance(currentTheme.palette, initialAppState.appearance).valid;
@@ -509439,6 +509577,13 @@ var BlunTUI = class {
509439
509577
  }), "warning");
509440
509578
  } catch {}
509441
509579
  }
509580
+ async refreshProviderModelsBeforeSession() {
509581
+ if ((await this.harness.getConfig()).models?.["blun/king"] === void 0) {
509582
+ await this.refreshProviderModelsInBackground();
509583
+ return;
509584
+ }
509585
+ this.refreshProviderModelsInBackground();
509586
+ }
509442
509587
  async finishStartup(shouldReplayHistory) {
509443
509588
  while (!this.aborted) {
509444
509589
  await this.flushStartupNotice();
@@ -509540,7 +509685,7 @@ var BlunTUI = class {
509540
509685
  return false;
509541
509686
  }
509542
509687
  if (this.startupWorkspaceSelectionPending) return false;
509543
- this.refreshProviderModelsInBackground();
509688
+ await this.refreshProviderModelsBeforeSession();
509544
509689
  const { startup } = this.options;
509545
509690
  const { workDir } = this.state.appState;
509546
509691
  let session;
@@ -509647,7 +509792,6 @@ var BlunTUI = class {
509647
509792
  this.channelQueueDeadline?.dispose();
509648
509793
  await this.telegramChannel?.stop();
509649
509794
  this.telegramChannel = void 0;
509650
- await Promise.allSettled(this.pendingAccountMemoryExtractions);
509651
509795
  this.streamingUI.discardPending();
509652
509796
  this.tasksBrowserController.close();
509653
509797
  this.btwPanelController.clear();
@@ -509753,9 +509897,11 @@ var BlunTUI = class {
509753
509897
  }
509754
509898
  }
509755
509899
  mountFooter() {
509900
+ if (this.footerMounted) return;
509756
509901
  const footerWrap = new GutterContainer(1, 1);
509757
509902
  footerWrap.addChild(this.state.footer);
509758
509903
  this.state.ui.addChild(footerWrap);
509904
+ this.footerMounted = true;
509759
509905
  }
509760
509906
  handlePlanToggle(next) {
509761
509907
  handlePlanCommand(this, next ? "on" : "off");
@@ -510155,9 +510301,7 @@ var BlunTUI = class {
510155
510301
  type: "text",
510156
510302
  text: modelInput
510157
510303
  }, imagePart] : modelInput;
510158
- session.promptAccepted(promptInput).then((result) => {
510159
- if (result.accepted) this.extractAccountMemory(displayText);
510160
- }).catch((error) => {
510304
+ session.promptAccepted(promptInput).catch((error) => {
510161
510305
  this.failSessionRequest(uiText("blunTui.session.sendFailed", { error: formatErrorMessage$2(error) }));
510162
510306
  });
510163
510307
  this.updateQueueDisplay();
@@ -510368,9 +510512,7 @@ var BlunTUI = class {
510368
510512
  model: BLUN_KING_MODEL_ALIAS,
510369
510513
  modelFallbackAllowed: false
510370
510514
  });
510371
- session.promptAccepted(sdkInput).then((result) => {
510372
- if (result.accepted) this.extractAccountMemory(input);
510373
- }).catch((error) => {
510515
+ session.promptAccepted(sdkInput).catch((error) => {
510374
510516
  finishPersonalMemoryRememberIntentTurn(session.id);
510375
510517
  const message = formatErrorMessage$2(error);
510376
510518
  this.failSessionRequest(uiText("blunTui.session.sendFailed", { error: message }));
@@ -510420,9 +510562,7 @@ var BlunTUI = class {
510420
510562
  content: part
510421
510563
  });
510422
510564
  const text = input.join("\n\n");
510423
- session.steer(text).then(() => {
510424
- this.extractAccountMemory(text);
510425
- }).catch((error) => {
510565
+ session.steer(text).catch((error) => {
510426
510566
  const message = formatErrorMessage$2(error);
510427
510567
  this.showError(uiText("blunTui.steer.failed", { error: message }));
510428
510568
  });
@@ -510512,51 +510652,8 @@ var BlunTUI = class {
510512
510652
  this.authFlow.refreshManagedQuotaWindows();
510513
510653
  }
510514
510654
  async refreshPersonalMemory(promptIfNeverAsked = false) {
510515
- const session = this.session;
510516
- if (session === void 0) return;
510517
- this.accountMemoryEnabledForSession = false;
510518
- try {
510519
- let consentStatus = await this.accountMemory.getConsentStatus();
510520
- if (consentStatus === "never_asked" && promptIfNeverAsked) {
510521
- const consent = await ensureStartupPersonalMemoryConsent({
510522
- host: this,
510523
- consentStatus,
510524
- copy: startupPersonalMemoryConsentCopy(),
510525
- updateSettings: async (patch) => {
510526
- await this.accountMemory.setConsent(patch.memory_enabled === true);
510527
- }
510528
- });
510529
- if (consent === "persistence-failed") this.showStatus(startupPersonalMemoryConsentCopy().persistenceFailed, "warning");
510530
- consentStatus = consent === "enabled" || consent === "disabled" ? consent : "never_asked";
510531
- }
510532
- this.accountMemoryEnabledForSession = consentStatus === "enabled";
510533
- } catch (error) {
510534
- this.reportAccountMemoryIssue("Account memory consent unavailable", error);
510535
- }
510536
- await this.applyAccountMemoryContext(session);
510537
- }
510538
- async applyAccountMemoryContext(session) {
510539
- try {
510540
- const context = this.accountMemoryEnabledForSession ? await this.accountMemory.loadSystemContext() : void 0;
510541
- await session.setAccountMemoryContext(context);
510542
- } catch (error) {
510543
- try {
510544
- await session.setAccountMemoryContext(void 0);
510545
- } catch {}
510546
- this.reportAccountMemoryIssue("Account memory unavailable", error);
510547
- }
510548
- }
510549
- extractAccountMemory(text) {
510550
- if (!this.accountMemoryEnabledForSession || text.trim().length === 0) return;
510551
- let pending;
510552
- pending = this.accountMemory.extract(text).catch((error) => {
510553
- this.reportAccountMemoryIssue("Account memory update failed", error);
510554
- }).finally(() => {
510555
- this.pendingAccountMemoryExtractions.delete(pending);
510556
- });
510557
- this.pendingAccountMemoryExtractions.add(pending);
510655
+ await this.personalMemoryController.refresh({ promptIfNeverAsked });
510558
510656
  }
510559
- reportAccountMemoryIssue(_label, _error) {}
510560
510657
  patchLivePane(patch) {
510561
510658
  if (!hasPatchChanges(this.state.livePane, patch)) return;
510562
510659
  Object.assign(this.state.livePane, patch);
@@ -510607,7 +510704,7 @@ var BlunTUI = class {
510607
510704
  this.registerSessionHandlers(session);
510608
510705
  this.syncAdditionalDirs(session);
510609
510706
  await this.authFlow.applyManagedAccountContextToSession();
510610
- await this.applyAccountMemoryContext(session);
510707
+ await this.personalMemoryController.refresh();
510611
510708
  }
510612
510709
  async syncRuntimeState(session = this.requireSession()) {
510613
510710
  const [status, goalResult] = await Promise.all([session.getStatus(), session.getGoal()]);
@@ -510816,7 +510913,7 @@ var BlunTUI = class {
510816
510913
  this.session = session;
510817
510914
  this.harness.setTelemetryContext({ sessionId: session.id });
510818
510915
  this.registerSessionHandlers(session);
510819
- await this.applyAccountMemoryContext(session);
510916
+ await this.personalMemoryController.refresh();
510820
510917
  await this.syncRuntimeState(session);
510821
510918
  this.updateTerminalTitle();
510822
510919
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blun-king-cli",
3
- "version": "9.1.23",
3
+ "version": "9.1.24",
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": {
@@ -67752,6 +67752,14 @@ function pidAlive(pid) {
67752
67752
  return false;
67753
67753
  }
67754
67754
  }
67755
+ /** True while the TUI process named by the lease still exists. */
67756
+ function isTuiLeaseOwnerAlive() {
67757
+ try {
67758
+ return pidAlive(parseInt(readFileSync(tuiPidFile(), "utf8"), 10));
67759
+ } catch {
67760
+ return false;
67761
+ }
67762
+ }
67755
67763
  function isTuiLeaseFresh(maxAgeMs = TUI_LEASE_MAX_AGE_MS, now = Date.now()) {
67756
67764
  try {
67757
67765
  const file = tuiPidFile();
@@ -67763,7 +67771,7 @@ function isTuiLeaseFresh(maxAgeMs = TUI_LEASE_MAX_AGE_MS, now = Date.now()) {
67763
67771
  }
67764
67772
  /** Per-message routing decision of the single poller. */
67765
67773
  function deliveryTarget() {
67766
- return isTuiLeaseFresh() ? "tui" : "headless";
67774
+ return isTuiLeaseOwnerAlive() ? "tui" : "headless";
67767
67775
  }
67768
67776
  function deliverToTuiQueue(envelope) {
67769
67777
  appendJsonl(tuiInboundQueueFile(), envelope);
@@ -67775,7 +67783,7 @@ function deliverToTuiQueue(envelope) {
67775
67783
  */
67776
67784
  function rotateStaleTuiQueue() {
67777
67785
  try {
67778
- if (!isTuiLeaseFresh() && existsSync(tuiInboundQueueFile())) rmSync(tuiInboundQueueFile());
67786
+ if (!isTuiLeaseOwnerAlive() && existsSync(tuiInboundQueueFile())) rmSync(tuiInboundQueueFile());
67779
67787
  } catch {}
67780
67788
  }
67781
67789
  //#endregion