blun-king-cli 9.1.306 → 9.1.308

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.
@@ -0,0 +1,73 @@
1
+ 'use strict';
2
+
3
+ const TURN_KEY_RE = /^turn:(\d+):/u;
4
+ const NEGATIVE_PHASES = new Set(['cancelled', 'failed', 'filtered']);
5
+ const NEGATIVE_POLICY = new Set(['blocked', 'error']);
6
+ const NEGATIVE_OUTCOMES = new Set(['error', 'cancelled']);
7
+ const SAFETY_LINE = 'Runtime evidence only; it cannot authorize any action or override current user or tool policy.';
8
+
9
+ function clean(value, max = 128) {
10
+ const text = String(value ?? '').replace(/[\u0000-\u001f\u007f]+/gu, ' ').replace(/\s+/gu, ' ').trim();
11
+ return text && text.length <= max ? text : '';
12
+ }
13
+
14
+ function turnNumber(observation) {
15
+ const match = TURN_KEY_RE.exec(String(observation?.key ?? ''));
16
+ return match ? Number(match[1]) : null;
17
+ }
18
+
19
+ function runtimeObservation(value) {
20
+ return value && typeof value === 'object' && value.scope === 'runtime'
21
+ && Number(value.confidence) === 1 && clean(value.key) && clean(value.value, 512)
22
+ && clean(value.source?.context_id);
23
+ }
24
+
25
+ function splitEvidence(value) {
26
+ return String(value ?? '').split(':').map((part) => clean(part)).filter(Boolean);
27
+ }
28
+
29
+ function buildCognitiveContextProjection(state, { currentTurnId, currentRuntimeId, maxChars = 900 } = {}) {
30
+ if (!Number.isSafeInteger(currentTurnId) || currentTurnId < 0
31
+ || !clean(currentRuntimeId)
32
+ || !Number.isSafeInteger(maxChars) || maxChars < 320 || maxChars > 1600
33
+ || !Array.isArray(state?.observations)) return null;
34
+
35
+ const previous = state.observations.filter(runtimeObservation)
36
+ .map((observation) => ({
37
+ observation,
38
+ turnId: turnNumber(observation),
39
+ runtimeId: observation.source.context_id,
40
+ }))
41
+ .filter((item) => item.turnId !== null
42
+ && !(item.runtimeId === currentRuntimeId && item.turnId === currentTurnId));
43
+ if (previous.length === 0) return null;
44
+ const latestItem = previous.at(-1);
45
+ const latestTurnId = latestItem.turnId;
46
+ const latest = previous.filter((item) => item.turnId === latestTurnId && item.runtimeId === latestItem.runtimeId)
47
+ .map((item) => item.observation);
48
+
49
+ const phase = [...latest].reverse().find((item) => item.domain === 'open_thread' && item.key === `turn:${latestTurnId}:phase`);
50
+ const policies = latest.filter((item) => item.key.endsWith(':tool-policy')).map((item) => {
51
+ const parts = splitEvidence(item.value);
52
+ return { decision: parts[0], tool: parts.at(-1) };
53
+ }).filter((item) => NEGATIVE_POLICY.has(item.decision) && item.tool);
54
+ const outcomes = latest.filter((item) => item.key.endsWith(':tool-result')).map((item) => {
55
+ const parts = splitEvidence(item.value);
56
+ return { outcome: parts[0], tool: parts.at(-1) };
57
+ }).filter((item) => NEGATIVE_OUTCOMES.has(item.outcome) && item.tool);
58
+ const actionable = phase && NEGATIVE_PHASES.has(phase.value) || policies.length > 0 || outcomes.length > 0;
59
+ if (!actionable) return null;
60
+
61
+ const lines = [`Previous runtime turn ${latestTurnId} needs inspection.`];
62
+ if (phase && NEGATIVE_PHASES.has(phase.value)) lines.push(`Turn outcome: ${phase.value}.`);
63
+ for (const item of policies.slice(-2)) lines.push(`Tool policy: ${item.tool} ${item.decision}.`);
64
+ for (const item of outcomes.slice(-2)) lines.push(`Tool outcome: ${item.tool} ${item.outcome}.`);
65
+ const next = [...latest].reverse().find((item) => item.domain === 'next_trigger'
66
+ && item.value !== 'await-next-input' && clean(item.value));
67
+ if (next) lines.push(`Next inspection trigger: ${clean(next.value)}.`);
68
+
69
+ while ([...lines, SAFETY_LINE].join('\n').length > maxChars && lines.length > 1) lines.splice(-1, 1);
70
+ return [...lines, SAFETY_LINE].join('\n');
71
+ }
72
+
73
+ module.exports = { buildCognitiveContextProjection };
@@ -2,6 +2,7 @@
2
2
 
3
3
  const crypto = require('node:crypto');
4
4
  const { openCognitiveStateStore } = require('./cognitive-state-store.cjs');
5
+ const { buildCognitiveContextProjection } = require('./cognitive-context-projection.cjs');
5
6
 
6
7
  const SAFE_ID_RE = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/u;
7
8
  const RIGHTS_AUTHORITIES = new Set(['runtime_user_prompt_hook', 'runtime_tool_policy']);
@@ -172,11 +173,22 @@ function createCognitiveTurnLifecycle({ home, tenantId, agentId, runtimeId, now
172
173
  ]);
173
174
  }
174
175
 
176
+ function projectForTurn(input) {
177
+ if (!exactKeys(input, new Set(['turnId']))) fail('COGNITIVE_LIFECYCLE_INVALID');
178
+ const turnId = safeTurnId(input.turnId);
179
+ if (turnId === null) fail('COGNITIVE_LIFECYCLE_INVALID');
180
+ return buildCognitiveContextProjection(store.read({ tenantId: tenant, agentId: agent }), {
181
+ currentTurnId: turnId,
182
+ currentRuntimeId: runtime,
183
+ });
184
+ }
185
+
175
186
  return {
176
187
  startTurn,
177
188
  recordRightsCheck,
178
189
  recordToolPolicy,
179
190
  recordToolResult,
191
+ projectForTurn,
180
192
  endTurn,
181
193
  read: () => store.read({ tenantId: tenant, agentId: agent }),
182
194
  verify: () => store.verify({ tenantId: tenant, agentId: agent }),
@@ -68,6 +68,7 @@ const RAW_ARGS = process.argv.slice(2);
68
68
  const PROFILE = parseProfileLaunchArgs(RAW_ARGS, launcherModeFromArgv(process.argv));
69
69
  const ARGS = PROFILE.args;
70
70
  const CORE_LOAD_TIMEOUT_MS = 30_000;
71
+ const RUNNING_UPDATE_RESUME_SESSION_ENV = 'BLUN_RUNNING_UPDATE_RESUME_SESSION_ID';
71
72
  const RUNTIME_READY_TIMEOUT_MS = 60_000;
72
73
  const RUNNING_UPDATE_RECHECK_MS = 5 * 60_000;
73
74
  const RUNNING_UPDATE_RECHECK_JITTER_MS = 2 * 60_000;
@@ -312,6 +313,7 @@ async function superviseProtectedCore(args, env, cwd, releaseNotice, options = {
312
313
  let pendingHandoff;
313
314
  let updateStarted = false;
314
315
  let runtimeReady = false;
316
+ let activeSession;
315
317
  let runtimeExitIntent = false;
316
318
  let supervisionCompleted = false;
317
319
  let runningUpdatePollTimer;
@@ -329,6 +331,23 @@ async function superviseProtectedCore(args, env, cwd, releaseNotice, options = {
329
331
  (options.clearTimeoutImpl || clearTimeout)(runningUpdatePollTimer);
330
332
  runningUpdatePollTimer = undefined;
331
333
  };
334
+ const validSessionMessage = (message) => typeof message?.sessionId === 'string'
335
+ && message.sessionId.length > 0
336
+ && message.sessionId.length <= 256
337
+ && !/[\u0000-\u001f\u007f]/u.test(message.sessionId)
338
+ && resolveHandoffCwd(message.cwd, '') !== '';
339
+ const stagedRuntimeOptions = () => ({
340
+ mode: runningUpdateMode,
341
+ ...(runningUpdateMode === RUNNING_UPDATE_MODES.RESUME && activeSession !== undefined
342
+ ? { cwd: activeSession.cwd, sessionId: activeSession.sessionId }
343
+ : {}),
344
+ });
345
+ const persistPreparedRuntime = () => {
346
+ if (preparedTarget === undefined) return;
347
+ const sharedHome = env.BLUN_SHARED_HOME;
348
+ if (typeof sharedHome !== 'string' || sharedHome.length === 0) return;
349
+ (options.stageRuntime || stageRuntime)(sharedHome, preparedTarget, stagedRuntimeOptions());
350
+ };
332
351
  const scheduleNextPreparation = (child) => {
333
352
  clearRunningUpdatePoll();
334
353
  if (supervisionCompleted || pendingHandoff !== undefined || updateStarted) return;
@@ -367,7 +386,7 @@ async function superviseProtectedCore(args, env, cwd, releaseNotice, options = {
367
386
  return;
368
387
  }
369
388
  preparedTarget = target;
370
- (options.stageRuntime || stageRuntime)(sharedHome, target, { mode: runningUpdateMode });
389
+ persistPreparedRuntime();
371
390
  const runningVersion = readPackageVersionAt(packageRoot);
372
391
  (options.recordRunningUpdateEvent || recordRunningUpdateEvent)(sharedHome, previousTarget === undefined ? {
373
392
  event: 'prepared-for-next-start',
@@ -396,13 +415,20 @@ async function superviseProtectedCore(args, env, cwd, releaseNotice, options = {
396
415
  && preparedTarget !== undefined
397
416
  && message.version === preparedTarget.version
398
417
  && message.mode === runningUpdateMode
399
- && typeof message.sessionId === 'string'
400
- && message.sessionId.length > 0
401
- && message.sessionId.length <= 256
402
- && !/[\u0000-\u001f\u007f]/u.test(message.sessionId)
403
- && resolveHandoffCwd(message.cwd, '') !== '') {
404
- pendingHandoff = Object.freeze({
418
+ && validSessionMessage(message)) {
419
+ activeSession = Object.freeze({
405
420
  cwd: resolveHandoffCwd(message.cwd, cwd),
421
+ sessionId: message.sessionId,
422
+ });
423
+ try {
424
+ persistPreparedRuntime();
425
+ } catch (error) {
426
+ options.onRunningUpdateError?.(error);
427
+ scheduleNextPreparation(child);
428
+ return;
429
+ }
430
+ pendingHandoff = Object.freeze({
431
+ cwd: activeSession.cwd,
406
432
  mode: message.mode,
407
433
  sessionId: message.sessionId,
408
434
  target: preparedTarget,
@@ -423,6 +449,17 @@ async function superviseProtectedCore(args, env, cwd, releaseNotice, options = {
423
449
  }
424
450
  }
425
451
  if (message?.type === RUNTIME_READY_MESSAGE) {
452
+ if (validSessionMessage(message)) {
453
+ activeSession = Object.freeze({
454
+ cwd: resolveHandoffCwd(message.cwd, cwd),
455
+ sessionId: message.sessionId,
456
+ });
457
+ try {
458
+ persistPreparedRuntime();
459
+ } catch (error) {
460
+ options.onRunningUpdateError?.(error);
461
+ }
462
+ }
426
463
  runtimeReady = true;
427
464
  refreshRunningUpdateMode();
428
465
  if (automaticMode()) startPreparation(child);
@@ -482,7 +519,11 @@ async function superviseProtectedCore(args, env, cwd, releaseNotice, options = {
482
519
  const handoffArgs = handoffArgsForMode(args, pendingHandoff.mode, pendingHandoff.sessionId);
483
520
  const handoffCwd = pendingHandoff.cwd;
484
521
  const previousActive = (options.readActiveRuntime || readActiveRuntime)(sharedHome);
485
- const nextCore = spawnCore(handoffArgs, env, handoffCwd, { packageRoot: target.packageRoot });
522
+ const resumeEnv = {
523
+ ...env,
524
+ [RUNNING_UPDATE_RESUME_SESSION_ENV]: pendingHandoff.sessionId,
525
+ };
526
+ const nextCore = spawnCore(handoffArgs, resumeEnv, handoffCwd, { packageRoot: target.packageRoot });
486
527
  const nextLoaded = await nextCore.loaded;
487
528
  const nextReady = nextLoaded && await (options.waitForRuntimeReady || waitForRuntimeReady)(
488
529
  nextCore,
@@ -498,7 +539,7 @@ async function superviseProtectedCore(args, env, cwd, releaseNotice, options = {
498
539
  toVersion: target.version,
499
540
  mode: pendingHandoff.mode,
500
541
  }, { now: options.nowImpl });
501
- return superviseProtectedCore(handoffArgs, env, handoffCwd, async () => {}, {
542
+ return superviseProtectedCore(handoffArgs, resumeEnv, handoffCwd, async () => {}, {
502
543
  ...options,
503
544
  existingCore: nextCore,
504
545
  packageRoot: target.packageRoot,
@@ -519,14 +560,14 @@ async function superviseProtectedCore(args, env, cwd, releaseNotice, options = {
519
560
  fromVersion: readPackageVersionAt(packageRoot),
520
561
  toVersion: target.version,
521
562
  }, { now: options.nowImpl });
522
- const fallback = spawnCore(handoffArgs, env, handoffCwd, { packageRoot });
563
+ const fallback = spawnCore(handoffArgs, resumeEnv, handoffCwd, { packageRoot });
523
564
  const fallbackLoaded = await fallback.loaded;
524
565
  const fallbackReady = fallbackLoaded && await (options.waitForRuntimeReady || waitForRuntimeReady)(
525
566
  fallback,
526
567
  options.readyTimeoutMs,
527
568
  );
528
569
  if (!fallbackReady) return 1;
529
- return superviseProtectedCore(handoffArgs, env, handoffCwd, async () => {}, {
570
+ return superviseProtectedCore(handoffArgs, resumeEnv, handoffCwd, async () => {}, {
530
571
  ...options,
531
572
  existingCore: fallback,
532
573
  packageRoot,
@@ -641,14 +682,20 @@ function spawnManagedLauncher(binary, cwd) {
641
682
  });
642
683
  }
643
684
 
644
- function spawnActiveLauncher(packageRoot, cwd, args = RAW_ARGS) {
685
+ function spawnActiveLauncher(packageRoot, cwd, args = RAW_ARGS, options = {}) {
645
686
  const entryName = path.basename(process.argv[1] || 'king.js').toLowerCase() === 'blun.js'
646
687
  ? 'blun.js'
647
688
  : 'king.js';
648
689
  return new Promise((resolve, reject) => {
649
690
  const child = spawn(process.execPath, [path.join(packageRoot, 'bin', entryName), ...args], {
650
691
  cwd,
651
- env: { ...process.env, BLUN_ACTIVE_RUNTIME_ROOT: packageRoot },
692
+ env: {
693
+ ...process.env,
694
+ BLUN_ACTIVE_RUNTIME_ROOT: packageRoot,
695
+ ...(typeof options.resumeSessionId === 'string' && options.resumeSessionId.length > 0
696
+ ? { [RUNNING_UPDATE_RESUME_SESSION_ENV]: options.resumeSessionId }
697
+ : {}),
698
+ },
652
699
  stdio: 'inherit',
653
700
  windowsHide: true,
654
701
  });
@@ -873,6 +920,9 @@ async function runLauncher(options = {}) {
873
920
  deferredPendingRuntime.packageRoot,
874
921
  deferredPendingRuntime.cwd || callerCwd,
875
922
  pendingArgs,
923
+ deferredPendingRuntime.mode === RUNNING_UPDATE_MODES.RESUME
924
+ ? { resumeSessionId: deferredPendingRuntime.sessionId }
925
+ : {},
876
926
  );
877
927
  return;
878
928
  }
package/blun.mjs CHANGED
@@ -261508,6 +261508,14 @@ var init_turn = __esmMin((() => {
261508
261508
  this.agent.telemetry.track("cognitive_lifecycle_error", { stage: method, error_type: error?.code ?? error?.name ?? "Error" });
261509
261509
  }
261510
261510
  }
261511
+ projectCognitiveState(turnId) {
261512
+ try {
261513
+ return this.getCognitiveLifecycle()?.projectForTurn({ turnId }) ?? null;
261514
+ } catch (error) {
261515
+ this.agent.telemetry.track("cognitive_lifecycle_error", { stage: "projectForTurn", error_type: error?.code ?? error?.name ?? "Error" });
261516
+ return null;
261517
+ }
261518
+ }
261511
261519
  prompt(input, origin = USER_PROMPT_ORIGIN) {
261512
261520
  return this.promptWithAcceptance(input, origin).turnId;
261513
261521
  }
@@ -262044,6 +262052,11 @@ var init_turn = __esmMin((() => {
262044
262052
  if (blunTurnNeedsInitialMcp(input, origin)) await this.agent.mcp?.waitForInitialLoad(signal);
262045
262053
  const personalMemoryRecall = await this.agent.injection.injectPersonalMemoryForTurn(turnId, input, origin, signal);
262046
262054
  await this.agent.injection.injectGoal();
262055
+ const cognitiveProjection = this.projectCognitiveState(turnId);
262056
+ if (cognitiveProjection !== null) this.agent.context.appendSystemReminder(cognitiveProjection, {
262057
+ kind: "injection",
262058
+ variant: "cognitive_continuity"
262059
+ });
262047
262060
  this.setActiveSteerAcceptance(turnId, true);
262048
262061
  const turnNeedsTools = blunTurnNeedsTools(input, origin);
262049
262062
  const turnHasAttachment = blunTurnHasAttachment(input);
@@ -515715,20 +515728,28 @@ const {
515715
515728
  readRunningUpdateMode,
515716
515729
  writeRunningUpdateMode
515717
515730
  } = __require("./bin/running-update-preference.cjs");
515718
- function notifyRunningRuntimeReady(tui) {
515719
- if (typeof process.env["BLUN_SHARED_HOME"] === "string" && process.env["BLUN_SHARED_HOME"].length > 0) void pruneRunningUpdateReleases(process.env["BLUN_SHARED_HOME"], {
515720
- activePackageRoot: __dirname
515721
- }).catch(() => {});
515731
+ const RUNNING_UPDATE_RESUME_SESSION_ENV = "BLUN_RUNNING_UPDATE_RESUME_SESSION_ID";
515732
+ function shouldContinueActiveGoalAfterRunningUpdate(expectedSessionId, currentSessionId, goalStatus) {
515733
+ return typeof expectedSessionId === "string" && expectedSessionId.length > 0 && currentSessionId === expectedSessionId && goalStatus === "active";
515734
+ }
515735
+ function sendRunningRuntimeSession(tui) {
515722
515736
  if (!process.connected) return;
515723
515737
  const sessionId = tui.getCurrentSessionId();
515724
515738
  if (sessionId.length === 0) return;
515725
515739
  try {
515726
515740
  process.send({
515727
515741
  type: RUNTIME_READY_MESSAGE,
515728
- sessionId
515742
+ sessionId,
515743
+ cwd: tui.state.appState.workDir
515729
515744
  });
515730
515745
  } catch {}
515731
515746
  }
515747
+ function notifyRunningRuntimeReady(tui) {
515748
+ if (typeof process.env["BLUN_SHARED_HOME"] === "string" && process.env["BLUN_SHARED_HOME"].length > 0) void pruneRunningUpdateReleases(process.env["BLUN_SHARED_HOME"], {
515749
+ activePackageRoot: __dirname
515750
+ }).catch(() => {});
515751
+ sendRunningRuntimeSession(tui);
515752
+ }
515732
515753
  function requestRunningUpdateAtSafeBoundary(tui) {
515733
515754
  if (tui.runningUpdateHandoffStarted || tui.isShuttingDown || !process.connected) return false;
515734
515755
  const version = tui.runningUpdatePreparedVersion;
@@ -516255,7 +516276,8 @@ var BlunTUI = class {
516255
516276
  if (this.session !== void 0) {
516256
516277
  this.sessionEventHandler.startSubscription();
516257
516278
  if (shouldReplayHistory) {
516258
- await this.promptStartupResumeGoalIfNeeded();
516279
+ const continuedAfterUpdate = this.continueActiveGoalAfterRunningUpdateIfNeeded();
516280
+ if (!continuedAfterUpdate) await this.promptStartupResumeGoalIfNeeded();
516259
516281
  if (this.aborted) return;
516260
516282
  }
516261
516283
  this.showSessionWarnings(this.session);
@@ -516405,6 +516427,13 @@ var BlunTUI = class {
516405
516427
  }
516406
516428
  if (choice === "resume") await handleGoalCommand(this, "resume");
516407
516429
  }
516430
+ continueActiveGoalAfterRunningUpdateIfNeeded() {
516431
+ const expectedSessionId = process.env[RUNNING_UPDATE_RESUME_SESSION_ENV];
516432
+ delete process.env[RUNNING_UPDATE_RESUME_SESSION_ENV];
516433
+ if (!shouldContinueActiveGoalAfterRunningUpdate(expectedSessionId, this.session?.id, this.state.appState.goal?.status)) return false;
516434
+ this.sendNormalUserInput(RESUME_GOAL_INPUT);
516435
+ return true;
516436
+ }
516408
516437
  async stop(exitCode) {
516409
516438
  if (this.isShuttingDown) return;
516410
516439
  this.isShuttingDown = true;
@@ -517900,6 +517929,7 @@ var BlunTUI = class {
517900
517929
  if (resumeState?.warning !== void 0) this.showStatus(uiText("blunTui.warning", { warning: resumeState.warning }), "warning");
517901
517930
  this.showStatus(statusMessage);
517902
517931
  this.showSessionWarnings(session);
517932
+ sendRunningRuntimeSession(this);
517903
517933
  }
517904
517934
  async reloadCurrentSessionView(session, statusMessage) {
517905
517935
  await this.personalMemoryController.clear(session);
@@ -517963,6 +517993,7 @@ var BlunTUI = class {
517963
517993
  this.showStatus(uiText("blunTui.session.started", { sessionId: session.id }));
517964
517994
  this.showSessionWarnings(session);
517965
517995
  this.showConfigWarningsIfAny();
517996
+ sendRunningRuntimeSession(this);
517966
517997
  }
517967
517998
  /** Surface config.toml load warnings (degraded or kept-previous config) in the status bar. */
517968
517999
  async showConfigWarningsIfAny() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blun-king-cli",
3
- "version": "9.1.306",
3
+ "version": "9.1.308",
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": {