blun-king-cli 9.1.421 → 9.1.422

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/LIESMICH.txt CHANGED
@@ -106,6 +106,8 @@ ausdrücklichen Trigger-Beleg. Andere Trigger-Arten dürfen kein `dueAt`
106
106
  enthalten. Ein Laufzeit-Wecker für eine durchgehend geöffnete Sitzung ist in
107
107
  diesem Release noch nicht enthalten.
108
108
 
109
+ Ab BLUN King 9.1.422 überwacht auch eine durchgehend geöffnete, untätige Sitzung ihren gespeicherten Zeit-Trigger. Sobald `dueAt` erreicht oder überschritten ist, fügt der Auto- oder God-Modus genau eine verborgene Fortsetzung in dieselbe Sitzung ein. Wird der Zeitpunkt während einer laufenden Antwort, Verdichtung, wartenden Nachricht, eines Befehls oder Dialogs fällig, wartet die Fortsetzung bis zum Leerlauf. Unmittelbar vor dem Start liest King Ziel, Checkpoint-Revision, Fälligkeit, Berechtigungsmodus und Sitzung erneut; ein geänderter oder veralteter Trigger kann deshalb nicht auslösen. Stoppen, Notausgang, Entladen und Wechseln der Sitzung entsorgen den Timer. Im manuellen Modus erfolgt kein selbstständiger Start.
110
+
109
111
  Zuverlässiger King-Start
110
112
  -----------------------
111
113
  Bei einer ausdrücklich als wiederholbar gekennzeichneten Serverüberlastung
package/README.md CHANGED
@@ -125,6 +125,8 @@ ausdrücklichen Trigger-Beleg. Andere Trigger-Arten dürfen kein `dueAt`
125
125
  enthalten. Ein Laufzeit-Wecker für eine durchgehend geöffnete Sitzung ist in
126
126
  diesem Release noch nicht enthalten.
127
127
 
128
+ Ab BLUN King 9.1.422 überwacht auch eine durchgehend geöffnete, untätige Sitzung ihren gespeicherten Zeit-Trigger. Sobald `dueAt` erreicht oder überschritten ist, fügt der Auto- oder God-Modus genau eine verborgene Fortsetzung in dieselbe Sitzung ein. Wird der Zeitpunkt während einer laufenden Antwort, Verdichtung, wartenden Nachricht, eines Befehls oder Dialogs fällig, wartet die Fortsetzung bis zum Leerlauf. Unmittelbar vor dem Start liest King Ziel, Checkpoint-Revision, Fälligkeit, Berechtigungsmodus und Sitzung erneut; ein geänderter oder veralteter Trigger kann deshalb nicht auslösen. Stoppen, Notausgang, Entladen und Wechseln der Sitzung entsorgen den Timer. Im manuellen Modus erfolgt kein selbstständiger Start.
129
+
128
130
  ## Zuverlässiger King-Start
129
131
 
130
132
  Bei einer vom Server ausdrücklich als wiederholbar gekennzeichneten
@@ -7,6 +7,40 @@ const START = Object.freeze({ kind: 'start', trigger: 'immediate' });
7
7
  const WAIT = Object.freeze({ kind: 'wait' });
8
8
  const CONTINUE = Object.freeze({ kind: 'continue' });
9
9
  const YIELD = Object.freeze({ kind: 'yield' });
10
+ const IGNORE = Object.freeze({ kind: 'ignore' });
11
+
12
+ function goalTimeTriggerDecision({ goal, permissionMode, now = new Date() } = {}) {
13
+ if (!goal || typeof goal !== 'object' || Array.isArray(goal)) return IGNORE;
14
+ if (goal.status !== 'active' || !AUTONOMOUS_PERMISSION_MODES.has(permissionMode)) return IGNORE;
15
+ if (typeof goal.goalId !== 'string' || goal.goalId.trim().length === 0) return IGNORE;
16
+
17
+ const checkpoint = goal.actionCheckpoint;
18
+ if (!checkpoint || typeof checkpoint !== 'object' || Array.isArray(checkpoint)) return IGNORE;
19
+ if (!Number.isInteger(checkpoint.revision) || checkpoint.revision < 1) return IGNORE;
20
+ if (checkpoint.phase !== 'wait') return IGNORE;
21
+ const trigger = checkpoint.nextTrigger;
22
+ if (!trigger || typeof trigger !== 'object' || Array.isArray(trigger)) return IGNORE;
23
+ if (trigger.kind !== 'time' || typeof trigger.condition !== 'string' || trigger.condition.trim().length === 0) return IGNORE;
24
+
25
+ const dueAt = String(trigger.dueAt ?? '');
26
+ if (!ISO_TIMESTAMP_RE.test(dueAt)) return IGNORE;
27
+ const dueAtMs = new Date(dueAt).getTime();
28
+ const nowMs = new Date(now).getTime();
29
+ if (!Number.isFinite(dueAtMs) || !Number.isFinite(nowMs)) return IGNORE;
30
+ const key = `${goal.goalId}:${checkpoint.revision}:${dueAt}`;
31
+ if (dueAtMs > nowMs) return Object.freeze({
32
+ kind: 'schedule',
33
+ key,
34
+ dueAt,
35
+ delayMs: dueAtMs - nowMs,
36
+ });
37
+ return Object.freeze({
38
+ kind: 'due',
39
+ key,
40
+ dueAt,
41
+ observation: `The checkpointed time trigger became due at ${dueAt}.`,
42
+ });
43
+ }
10
44
 
11
45
  function goalAutostartDecision({ goal, permissionMode, now = new Date() } = {}) {
12
46
  if (!goal || typeof goal !== 'object' || Array.isArray(goal)) return WAIT;
@@ -18,18 +52,13 @@ function goalAutostartDecision({ goal, permissionMode, now = new Date() } = {})
18
52
  if (!trigger || typeof trigger !== 'object' || Array.isArray(trigger)) return WAIT;
19
53
  if (typeof trigger.condition !== 'string' || trigger.condition.trim().length === 0) return WAIT;
20
54
  if (ACTIVE_PHASES.has(checkpoint.phase) && trigger.kind === 'immediate') return START;
21
- if (checkpoint.phase !== 'wait' || trigger.kind !== 'time') return WAIT;
22
-
23
- const dueAt = String(trigger.dueAt ?? '');
24
- if (!ISO_TIMESTAMP_RE.test(dueAt)) return WAIT;
25
- const dueAtMs = new Date(dueAt).getTime();
26
- const nowMs = new Date(now).getTime();
27
- if (!Number.isFinite(dueAtMs) || !Number.isFinite(nowMs) || dueAtMs > nowMs) return WAIT;
55
+ const timed = goalTimeTriggerDecision({ goal, permissionMode, now });
56
+ if (timed.kind !== 'due') return WAIT;
28
57
  return Object.freeze({
29
58
  kind: 'start',
30
59
  trigger: 'time',
31
- dueAt,
32
- observation: `The checkpointed time trigger became due at ${dueAt}.`,
60
+ dueAt: timed.dueAt,
61
+ observation: timed.observation,
33
62
  });
34
63
  }
35
64
 
@@ -40,4 +69,4 @@ function goalContinuationDecision({ goal } = {}) {
40
69
  return checkpoint.phase === 'wait' ? YIELD : CONTINUE;
41
70
  }
42
71
 
43
- module.exports = { goalAutostartDecision, goalContinuationDecision };
72
+ module.exports = { goalAutostartDecision, goalContinuationDecision, goalTimeTriggerDecision };
@@ -0,0 +1,146 @@
1
+ 'use strict';
2
+
3
+ const { goalTimeTriggerDecision } = require('./cognitive-goal-autostart-policy.cjs');
4
+
5
+ const MAX_TIMER_DELAY_MS = 2_147_483_647;
6
+
7
+ class GoalTimeTriggerController {
8
+ #readState;
9
+ #start;
10
+ #now;
11
+ #setTimer;
12
+ #clearTimer;
13
+ #onError;
14
+ #retryDelayMs;
15
+ #timer;
16
+ #timerKey;
17
+ #inFlightKey;
18
+ #startedKey;
19
+ #generation = 0;
20
+ #disposed = false;
21
+
22
+ constructor({
23
+ readState,
24
+ start,
25
+ now = () => new Date(),
26
+ setTimer = setTimeout,
27
+ clearTimer = clearTimeout,
28
+ onError = () => {},
29
+ retryDelayMs = 1_000,
30
+ } = {}) {
31
+ if (typeof readState !== 'function') throw new TypeError('readState must be a function');
32
+ if (typeof start !== 'function') throw new TypeError('start must be a function');
33
+ this.#readState = readState;
34
+ this.#start = start;
35
+ this.#now = now;
36
+ this.#setTimer = setTimer;
37
+ this.#clearTimer = clearTimer;
38
+ this.#onError = onError;
39
+ this.#retryDelayMs = retryDelayMs;
40
+ }
41
+
42
+ sync() {
43
+ if (this.#disposed) return undefined;
44
+ const state = this.#readState() ?? {};
45
+ const decision = goalTimeTriggerDecision({
46
+ goal: state.goal,
47
+ permissionMode: state.permissionMode,
48
+ now: this.#now(),
49
+ });
50
+ if (decision.kind === 'ignore' || typeof state.sessionId !== 'string' || state.sessionId.length === 0) {
51
+ this.#clearScheduledTimer();
52
+ return undefined;
53
+ }
54
+
55
+ const key = `${state.sessionId}:${decision.key}`;
56
+ if (this.#startedKey === key || this.#inFlightKey === key) {
57
+ this.#clearScheduledTimer();
58
+ return undefined;
59
+ }
60
+ if (decision.kind === 'schedule') {
61
+ this.#schedule(key, Math.min(MAX_TIMER_DELAY_MS, Math.max(0, decision.delayMs)));
62
+ return undefined;
63
+ }
64
+
65
+ this.#clearScheduledTimer();
66
+ if (state.idle !== true) return undefined;
67
+ return this.#startDueTrigger(key, decision);
68
+ }
69
+
70
+ reset() {
71
+ this.#generation += 1;
72
+ this.#clearScheduledTimer();
73
+ this.#inFlightKey = undefined;
74
+ this.#startedKey = undefined;
75
+ }
76
+
77
+ dispose() {
78
+ this.reset();
79
+ this.#disposed = true;
80
+ }
81
+
82
+ #schedule(key, delayMs) {
83
+ if (this.#timer !== undefined && this.#timerKey === key) return;
84
+ this.#clearScheduledTimer();
85
+ this.#timerKey = key;
86
+ this.#timer = this.#setTimer(() => {
87
+ this.#timer = undefined;
88
+ this.#timerKey = undefined;
89
+ void this.sync();
90
+ }, delayMs);
91
+ this.#timer?.unref?.();
92
+ }
93
+
94
+ #startDueTrigger(key, decision) {
95
+ const generation = this.#generation;
96
+ this.#inFlightKey = key;
97
+ const trigger = {
98
+ key,
99
+ trigger: 'time',
100
+ dueAt: decision.dueAt,
101
+ observation: decision.observation,
102
+ };
103
+ let result;
104
+ try {
105
+ result = this.#start(trigger);
106
+ } catch (error) {
107
+ result = Promise.reject(error);
108
+ }
109
+ return Promise.resolve(result).then((accepted) => {
110
+ if (this.#disposed || generation !== this.#generation || this.#inFlightKey !== key) return false;
111
+ this.#inFlightKey = undefined;
112
+ if (accepted === true) {
113
+ this.#startedKey = key;
114
+ return true;
115
+ }
116
+ this.#scheduleRetry(key);
117
+ return false;
118
+ }, (error) => {
119
+ if (!this.#disposed && generation === this.#generation && this.#inFlightKey === key) {
120
+ this.#inFlightKey = undefined;
121
+ this.#onError(error);
122
+ this.#scheduleRetry(key);
123
+ }
124
+ return false;
125
+ });
126
+ }
127
+
128
+ #scheduleRetry(key) {
129
+ const state = this.#readState() ?? {};
130
+ const decision = goalTimeTriggerDecision({
131
+ goal: state.goal,
132
+ permissionMode: state.permissionMode,
133
+ now: this.#now(),
134
+ });
135
+ if (decision.kind !== 'due' || `${state.sessionId}:${decision.key}` !== key) return;
136
+ this.#schedule(key, this.#retryDelayMs);
137
+ }
138
+
139
+ #clearScheduledTimer() {
140
+ if (this.#timer !== undefined) this.#clearTimer(this.#timer);
141
+ this.#timer = undefined;
142
+ this.#timerKey = undefined;
143
+ }
144
+ }
145
+
146
+ module.exports = { GoalTimeTriggerController, MAX_TIMER_DELAY_MS };
package/blun.mjs CHANGED
@@ -7,6 +7,7 @@ const __dirname = __cjsShimDirname(__filename);
7
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
+ import cognitiveGoalTimeTriggerController from "./bin/cognitive-goal-time-trigger-controller.cjs";
10
11
  import crypto$1, { createHash, randomBytes, randomInt, randomUUID, timingSafeEqual } from "node:crypto";
11
12
  import * as fs$16 from "node:fs";
12
13
  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";
@@ -38,6 +39,7 @@ import { createServer } from "node:http";
38
39
  import { pipeline as pipeline$1 } from "node:stream/promises";
39
40
  const { writeTelegramConsoleStatus } = telegramConsoleStatusPolicy;
40
41
  const { goalAutostartDecision, goalContinuationDecision } = cognitiveGoalAutostartPolicy;
42
+ const { GoalTimeTriggerController } = cognitiveGoalTimeTriggerController;
41
43
  import { EventEmitter as EventEmitter$1 } from "node:events";
42
44
  import { StringDecoder } from "node:string_decoder";
43
45
  import co from "node:assert";
@@ -507786,6 +507788,7 @@ var SessionEventHandler = class {
507786
507788
  this.handleEvent(event, sendQueued);
507787
507789
  });
507788
507790
  this.syncMcpServerStatusSnapshot(session);
507791
+ this.host.syncGoalTimeTrigger();
507789
507792
  }
507790
507793
  async syncMcpServerStatusSnapshot(session) {
507791
507794
  const { host } = this;
@@ -508289,6 +508292,7 @@ var SessionEventHandler = class {
508289
508292
  }
508290
508293
  handleGoalUpdated(event) {
508291
508294
  this.host.setAppState({ goal: event.snapshot });
508295
+ this.host.syncGoalTimeTrigger();
508292
508296
  if (event.snapshot === null && this.goalCompletionAwaitingClear) {
508293
508297
  this.goalCompletionAwaitingClear = false;
508294
508298
  this.queuedGoalPromotionPending = true;
@@ -516160,6 +516164,7 @@ var BlunTUI = class {
516160
516164
  personalMemoryController;
516161
516165
  customerMistakeController;
516162
516166
  managedQuotaWarningController;
516167
+ goalTimeTriggerController;
516163
516168
  managedQuotaWarningPersistence = Promise.resolve();
516164
516169
  footerMounted = false;
516165
516170
  /** Timer that auto-clears the one-shot "moved to background" footer hint. */
@@ -516263,6 +516268,13 @@ var BlunTUI = class {
516263
516268
  }
516264
516269
  }));
516265
516270
  this.streamingUI = new StreamingUIController(this);
516271
+ this.goalTimeTriggerController = new GoalTimeTriggerController({
516272
+ readState: () => this.readGoalTimeTriggerState(),
516273
+ start: (trigger) => this.startAutonomousGoalContinuation(trigger),
516274
+ onError: (error) => {
516275
+ this.track("goal_time_trigger_failed", { error_type: error?.code ?? error?.name ?? "Error" });
516276
+ }
516277
+ });
516266
516278
  this.authFlow = new AuthFlowController(this);
516267
516279
  this.personalMemoryController = new PersonalMemoryController(this);
516268
516280
  this.customerMistakeController = new CustomerMistakeHostController();
@@ -516685,6 +516697,41 @@ var BlunTUI = class {
516685
516697
  });
516686
516698
  return result.kind === "selected" ? result.path : void 0;
516687
516699
  }
516700
+ readGoalTimeTriggerState() {
516701
+ return {
516702
+ sessionId: this.session?.id ?? "",
516703
+ goal: this.state.appState.goal,
516704
+ permissionMode: this.state.appState.permissionMode,
516705
+ idle: this.session !== void 0 && !this.isShuttingDown && !this.aborted && !this.queueCommandRunning && this.queueSteerInFlight === void 0 && !this.editorReplacementActive && !this.deferUserMessages && !this.streamingUI.hasActiveTurn() && this.state.appState.streamingPhase === "idle" && !this.state.appState.isCompacting && this.state.queuedMessages.length === 0
516706
+ };
516707
+ }
516708
+ syncGoalTimeTrigger() {
516709
+ if (this.isShuttingDown) return;
516710
+ void this.goalTimeTriggerController?.sync();
516711
+ }
516712
+ async startAutonomousGoalContinuation(autostart) {
516713
+ const session = this.session;
516714
+ if (session === void 0) return false;
516715
+ const sessionId = session.id;
516716
+ const autostartPrompt = autostart.observation === void 0 ? GOAL_CONTINUATION_PROMPT : `${GOAL_CONTINUATION_PROMPT}\n\n${autostart.observation}`;
516717
+ this.beginSessionRequest();
516718
+ this.setAppState({
516719
+ model: BLUN_KING_MODEL_ALIAS,
516720
+ modelFallbackAllowed: false
516721
+ });
516722
+ try {
516723
+ const result = await session.promptAccepted(autostartPrompt);
516724
+ if (!result.accepted && this.session?.id === sessionId) {
516725
+ this.setAppState({ streamingPhase: "idle" });
516726
+ this.resetLivePane();
516727
+ this.track("goal_autostart", { outcome: "not_accepted" });
516728
+ }
516729
+ return result.accepted === true;
516730
+ } catch (error) {
516731
+ if (this.session?.id === sessionId) this.failSessionRequest(uiText("blunTui.session.sendFailed", { error: formatErrorMessage$2(error) }));
516732
+ return false;
516733
+ }
516734
+ }
516688
516735
  async promptStartupResumeGoalIfNeeded() {
516689
516736
  const session = this.session;
516690
516737
  const goal = this.state.appState.goal;
@@ -516696,25 +516743,12 @@ var BlunTUI = class {
516696
516743
  now: new Date()
516697
516744
  });
516698
516745
  if (autostart.kind === "start") {
516699
- const autostartPrompt = autostart.observation === void 0 ? GOAL_CONTINUATION_PROMPT : `${GOAL_CONTINUATION_PROMPT}\n\n${autostart.observation}`;
516700
516746
  this.startupGoalPromptedSessionId = sessionId;
516701
- this.beginSessionRequest();
516702
- this.setAppState({
516703
- model: BLUN_KING_MODEL_ALIAS,
516704
- modelFallbackAllowed: false
516705
- });
516706
- try {
516707
- const result = await session.promptAccepted(autostartPrompt);
516708
- if (!result.accepted && this.session?.id === sessionId) {
516709
- this.setAppState({ streamingPhase: "idle" });
516710
- this.resetLivePane();
516711
- this.track("goal_autostart", { outcome: "not_accepted" });
516712
- }
516713
- } catch (error) {
516714
- if (this.session?.id === sessionId) this.failSessionRequest(uiText("blunTui.session.sendFailed", { error: formatErrorMessage$2(error) }));
516715
- }
516747
+ if (autostart.trigger === "time") await this.goalTimeTriggerController.sync();
516748
+ else await this.startAutonomousGoalContinuation(autostart);
516716
516749
  return;
516717
516750
  }
516751
+ this.syncGoalTimeTrigger();
516718
516752
  if (goal.status !== "paused" && goal.status !== "blocked") return;
516719
516753
  this.startupGoalPromptedSessionId = sessionId;
516720
516754
  const choice = await promptStartupResumeGoal(this, goal.objective, startupResumeGoalPromptCopy());
@@ -516728,6 +516762,7 @@ var BlunTUI = class {
516728
516762
  async stop(exitCode) {
516729
516763
  if (this.isShuttingDown) return;
516730
516764
  this.isShuttingDown = true;
516765
+ this.goalTimeTriggerController.dispose();
516731
516766
  await this.flushInputDraft();
516732
516767
  this.unregisterSignalHandlers();
516733
516768
  this.aborted = true;
@@ -516807,6 +516842,7 @@ var BlunTUI = class {
516807
516842
  }
516808
516843
  emergencyTerminalExit(exitCode = 129) {
516809
516844
  this.isShuttingDown = true;
516845
+ this.goalTimeTriggerController.dispose();
516810
516846
  this.unregisterSignalHandlers();
516811
516847
  this.channelQueueDeadline?.dispose();
516812
516848
  this.directFocusController?.dispose();
@@ -518154,6 +518190,7 @@ var BlunTUI = class {
518154
518190
  if (!hasPatchChanges(this.state.appState, effectivePatch)) return;
518155
518191
  const additionalDirsChanged = "additionalDirs" in effectivePatch && !sameStringArrays(this.state.appState.additionalDirs, effectivePatch.additionalDirs ?? []);
518156
518192
  const busyChanged = "streamingPhase" in effectivePatch || "isCompacting" in effectivePatch;
518193
+ const goalTimeTriggerChanged = busyChanged || "permissionMode" in effectivePatch;
518157
518194
  Object.assign(this.state.appState, effectivePatch);
518158
518195
  if ("planMode" in effectivePatch) this.updateEditorBorderHighlight();
518159
518196
  this.state.loopIndicator.setState(this.state.appState);
@@ -518165,6 +518202,7 @@ var BlunTUI = class {
518165
518202
  this.updateQueueDisplay();
518166
518203
  this.sessionEventHandler.retryQueuedGoalPromotion();
518167
518204
  }
518205
+ if (goalTimeTriggerChanged) queueMicrotask(() => this.syncGoalTimeTrigger());
518168
518206
  if (additionalDirsChanged) this.setupAutocomplete();
518169
518207
  this.state.ui.requestRender();
518170
518208
  }
@@ -518303,6 +518341,7 @@ var BlunTUI = class {
518303
518341
  }
518304
518342
  unloadCurrentSession(reason) {
518305
518343
  const previous = this.session;
518344
+ this.goalTimeTriggerController.reset();
518306
518345
  this.managedQuotaWarningController.dismiss();
518307
518346
  this.sessionEventUnsubscribe?.();
518308
518347
  this.sessionEventUnsubscribe = void 0;
@@ -518355,6 +518394,7 @@ var BlunTUI = class {
518355
518394
  }
518356
518395
  resetSessionRuntime() {
518357
518396
  this.aborted = false;
518397
+ this.goalTimeTriggerController.reset();
518358
518398
  this.managedQuotaWarningController.dismiss();
518359
518399
  this.streamingUI.discardPending();
518360
518400
  this.streamingUI.disposeActiveCompactionBlock();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blun-king-cli",
3
- "version": "9.1.421",
3
+ "version": "9.1.422",
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": {