blun-king-cli 9.1.417 → 9.1.419

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
@@ -75,6 +75,21 @@ sämtliche zurückgegebenen Nachrichten-IDs zusammen mit dem vollständigen Text
75
75
  Schlägt ein Teil fehl, endet die Zustellung an dieser Stelle, statt die Antwort
76
76
  fälschlich als vollständig zu melden.
77
77
 
78
+ Ab BLUN King 9.1.418 übernimmt der Identitätsgraph die von Telegram bestätigte
79
+ Unterscheidung zwischen Menschen und Bots. Bereits vorhandene
80
+ Telegram-Kontakte, die mangels dieses Merkmals als Menschen angelegt wurden,
81
+ werden bei der nächsten eindeutig als Bot bestätigten Nachricht einmalig als
82
+ Agent korrigiert. Alle Rollen, Zuständigkeiten, Beziehungsnotizen und sonstigen
83
+ Felder bleiben unverändert. Ein Agent wird niemals zu einer Person
84
+ zurückgestuft; Namen oder Benutzernamen dienen nicht als Beweis.
85
+
86
+ Ab BLUN King 9.1.419 setzt eine natürlich fortgesetzte Sitzung ein bereits
87
+ aktives Ziel selbstständig fort, wenn dessen gespeicherter nächster Auslöser
88
+ ausdrücklich sofort gilt und Auto- oder God-Modus aktiv ist. Die Fortsetzung
89
+ erscheint nicht als erfundene Benutzernachricht und wird pro Sitzung nur einmal
90
+ angestoßen. Wartende, pausierte und blockierte Ziele sowie manuelle
91
+ Berechtigungsmodi starten weiterhin nicht selbstständig.
92
+
78
93
  Zuverlässiger King-Start
79
94
  -----------------------
80
95
  Bei einer ausdrücklich als wiederholbar gekennzeichneten Serverüberlastung
package/README.md CHANGED
@@ -90,10 +90,25 @@ sämtliche zurückgegebenen Nachrichten-IDs zusammen mit dem vollständigen Text
90
90
  Schlägt ein Teil fehl, endet die Zustellung an dieser Stelle, statt die Antwort
91
91
  fälschlich als vollständig zu melden.
92
92
 
93
+ Ab BLUN King 9.1.418 übernimmt der Identitätsgraph die von Telegram bestätigte
94
+ Unterscheidung zwischen Menschen und Bots. Bereits vorhandene
95
+ Telegram-Kontakte, die mangels dieses Merkmals als Menschen angelegt wurden,
96
+ werden bei der nächsten eindeutig als Bot bestätigten Nachricht einmalig als
97
+ Agent korrigiert. Alle Rollen, Zuständigkeiten, Beziehungsnotizen und sonstigen
98
+ Felder bleiben unverändert. Ein Agent wird niemals zu einer Person
99
+ zurückgestuft; Namen oder Benutzernamen dienen nicht als Beweis.
100
+
93
101
  `Strg+C` und `Esc` brechen einen aktiven Zug zuverlässig ab, ohne den bereits
94
102
  geschriebenen Entwurf zu löschen. Das gilt auch bei Autovervollständigung,
95
103
  Geistervorschlägen, Bash-Eingabe und einer noch offenen Mehrzeileneingabe.
96
104
 
105
+ Ab BLUN King 9.1.419 setzt eine natürlich fortgesetzte Sitzung ein bereits
106
+ aktives Ziel selbstständig fort, wenn dessen gespeicherter nächster Auslöser
107
+ ausdrücklich sofort gilt und Auto- oder God-Modus aktiv ist. Die Fortsetzung
108
+ erscheint nicht als erfundene Benutzernachricht und wird pro Sitzung nur einmal
109
+ angestoßen. Wartende, pausierte und blockierte Ziele sowie manuelle
110
+ Berechtigungsmodi starten weiterhin nicht selbstständig.
111
+
97
112
  ## Zuverlässiger King-Start
98
113
 
99
114
  Bei einer vom Server ausdrücklich als wiederholbar gekennzeichneten
@@ -0,0 +1,22 @@
1
+ 'use strict';
2
+
3
+ const AUTONOMOUS_PERMISSION_MODES = new Set(['auto', 'yolo']);
4
+ const ACTIVE_PHASES = new Set(['orient', 'plan', 'act', 'verify', 'learn']);
5
+ const START = Object.freeze({ kind: 'start', trigger: 'immediate' });
6
+ const WAIT = Object.freeze({ kind: 'wait' });
7
+
8
+ function goalAutostartDecision({ goal, permissionMode } = {}) {
9
+ if (!goal || typeof goal !== 'object' || Array.isArray(goal)) return WAIT;
10
+ if (goal.status !== 'active' || !AUTONOMOUS_PERMISSION_MODES.has(permissionMode)) return WAIT;
11
+
12
+ const checkpoint = goal.actionCheckpoint;
13
+ if (!checkpoint || typeof checkpoint !== 'object' || Array.isArray(checkpoint)) return WAIT;
14
+ if (!ACTIVE_PHASES.has(checkpoint.phase)) return WAIT;
15
+ const trigger = checkpoint.nextTrigger;
16
+ if (!trigger || typeof trigger !== 'object' || Array.isArray(trigger)) return WAIT;
17
+ if (trigger.kind !== 'immediate') return WAIT;
18
+ if (typeof trigger.condition !== 'string' || trigger.condition.trim().length === 0) return WAIT;
19
+ return START;
20
+ }
21
+
22
+ module.exports = { goalAutostartDecision };
@@ -93,6 +93,51 @@ function createJsonOnce(root, segments, value) {
93
93
  }
94
94
  }
95
95
 
96
+ function updateJsonAtomically(root, segments, update) {
97
+ const target = path.resolve(root, ...segments);
98
+ if (!isInside(root, target)) return false;
99
+ let handle;
100
+ let temp = '';
101
+ try {
102
+ const stat = fs.lstatSync(target);
103
+ if (!stat.isFile() || stat.isSymbolicLink() || stat.size > MAX_FILE_BYTES) return false;
104
+ const original = fs.readFileSync(target, 'utf8');
105
+ const current = JSON.parse(original);
106
+ if (!current || typeof current !== 'object' || Array.isArray(current)) return false;
107
+ const next = update(current);
108
+ if (!next || typeof next !== 'object' || Array.isArray(next)) return false;
109
+ const serialized = `${JSON.stringify(next, null, 2)}\n`;
110
+ if (Buffer.byteLength(serialized, 'utf8') > MAX_FILE_BYTES) return false;
111
+
112
+ const parent = path.dirname(target);
113
+ const realParent = fs.realpathSync(parent);
114
+ if (!isInside(root, realParent)) return false;
115
+ temp = path.join(parent, `.${path.basename(target)}.${process.pid}.${Date.now()}.tmp`);
116
+ handle = fs.openSync(temp, 'wx', 0o600);
117
+ fs.writeFileSync(handle, serialized, 'utf8');
118
+ fs.fsyncSync(handle);
119
+ fs.closeSync(handle);
120
+ handle = undefined;
121
+
122
+ const currentStat = fs.lstatSync(target);
123
+ if (!currentStat.isFile() || currentStat.isSymbolicLink() || fs.readFileSync(target, 'utf8') !== original) return false;
124
+ fs.renameSync(temp, target);
125
+ temp = '';
126
+ return true;
127
+ } catch {
128
+ return false;
129
+ } finally {
130
+ if (handle !== undefined) fs.closeSync(handle);
131
+ if (temp) {
132
+ try { fs.rmSync(temp, { force: true }); } catch {}
133
+ }
134
+ }
135
+ }
136
+
137
+ function isExplicitTelegramBot(meta) {
138
+ return meta?.is_bot === true || String(meta?.is_bot ?? '').toLowerCase() === 'true';
139
+ }
140
+
96
141
  function cleanText(value, maxChars = 240) {
97
142
  if (typeof value !== 'string') return '';
98
143
  return value.replace(/[\u0000-\u001f\u007f]+/gu, ' ').replace(/\s+/gu, ' ').trim().slice(0, maxChars);
@@ -485,19 +530,31 @@ function recordChannelIdentity(envelope, env = process.env) {
485
530
  const firstSeenAt = trustedTimestamp(meta.ts);
486
531
  const receivedAt = trustedTimestamp(meta.received_at) || firstSeenAt;
487
532
  const created = [];
488
- if (createJsonOnce(root, ['actors', `${actorId}.json`], {
533
+ const updated = [];
534
+ const actorSegments = ['actors', `${actorId}.json`];
535
+ const actorCreated = createJsonOnce(root, actorSegments, {
489
536
  version: 1,
490
537
  actor_id: actorId,
491
538
  provider: 'telegram',
492
539
  provider_subject_id: subjectId,
493
540
  display_name: displayName,
494
- kind: meta.is_bot === true || String(meta.is_bot ?? '').toLowerCase() === 'true' ? 'agent' : 'person',
541
+ kind: isExplicitTelegramBot(meta) ? 'agent' : 'person',
495
542
  role: '',
496
543
  responsibilities: [],
497
544
  traits: [],
498
545
  aliases: [],
499
546
  ...(firstSeenAt ? { first_seen_at: firstSeenAt } : {}),
500
- })) created.push('actor');
547
+ });
548
+ if (actorCreated) created.push('actor');
549
+ else if (isExplicitTelegramBot(meta) && updateJsonAtomically(root, actorSegments, (actor) => (
550
+ actor.version === 1
551
+ && actor.actor_id === actorId
552
+ && actor.provider === 'telegram'
553
+ && actor.provider_subject_id === subjectId
554
+ && actor.kind === 'person'
555
+ ? { ...actor, kind: 'agent' }
556
+ : undefined
557
+ ))) updated.push('actor_kind');
501
558
  if (createJsonOnce(root, ['agents', agentId, 'relationships', `${actorId}.json`], {
502
559
  version: 1,
503
560
  actor_id: actorId,
@@ -621,6 +678,7 @@ function recordChannelIdentity(envelope, env = process.env) {
621
678
  actor_id: actorId,
622
679
  ...(groupId ? { group_id: groupId } : {}),
623
680
  created,
681
+ updated,
624
682
  ...(learning ? { learning } : {}),
625
683
  ...(journal ? { journal } : {}),
626
684
  model_context: modelContext,
package/blun.mjs CHANGED
@@ -6,6 +6,7 @@ const __filename = __cjsShimFileURLToPath(import.meta.url);
6
6
  const __dirname = __cjsShimDirname(__filename);
7
7
  import { createRequire } from "node:module";
8
8
  import telegramConsoleStatusPolicy from "./bin/telegram-console-status-policy.cjs";
9
+ import cognitiveGoalAutostartPolicy from "./bin/cognitive-goal-autostart-policy.cjs";
9
10
  import crypto$1, { createHash, randomBytes, randomInt, randomUUID, timingSafeEqual } from "node:crypto";
10
11
  import * as fs$16 from "node:fs";
11
12
  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";
@@ -36,6 +37,7 @@ import * as win32Path from "node:path/win32";
36
37
  import { createServer } from "node:http";
37
38
  import { pipeline as pipeline$1 } from "node:stream/promises";
38
39
  const { writeTelegramConsoleStatus } = telegramConsoleStatusPolicy;
40
+ const { goalAutostartDecision } = cognitiveGoalAutostartPolicy;
39
41
  import { EventEmitter as EventEmitter$1 } from "node:events";
40
42
  import { StringDecoder } from "node:string_decoder";
41
43
  import co from "node:assert";
@@ -516666,8 +516668,32 @@ var BlunTUI = class {
516666
516668
  async promptStartupResumeGoalIfNeeded() {
516667
516669
  const session = this.session;
516668
516670
  const goal = this.state.appState.goal;
516669
- if (session === void 0 || goal === null || goal === void 0 || goal.status !== "paused" && goal.status !== "blocked" || this.startupGoalPromptedSessionId === session.id) return;
516671
+ if (session === void 0 || goal === null || goal === void 0 || this.startupGoalPromptedSessionId === session.id) return;
516670
516672
  const sessionId = session.id;
516673
+ const autostart = goalAutostartDecision({
516674
+ goal,
516675
+ permissionMode: this.state.appState.permissionMode
516676
+ });
516677
+ if (autostart.kind === "start") {
516678
+ this.startupGoalPromptedSessionId = sessionId;
516679
+ this.beginSessionRequest();
516680
+ this.setAppState({
516681
+ model: BLUN_KING_MODEL_ALIAS,
516682
+ modelFallbackAllowed: false
516683
+ });
516684
+ try {
516685
+ const result = await session.promptAccepted(GOAL_CONTINUATION_PROMPT);
516686
+ if (!result.accepted && this.session?.id === sessionId) {
516687
+ this.setAppState({ streamingPhase: "idle" });
516688
+ this.resetLivePane();
516689
+ this.track("goal_autostart", { outcome: "not_accepted" });
516690
+ }
516691
+ } catch (error) {
516692
+ if (this.session?.id === sessionId) this.failSessionRequest(uiText("blunTui.session.sendFailed", { error: formatErrorMessage$2(error) }));
516693
+ }
516694
+ return;
516695
+ }
516696
+ if (goal.status !== "paused" && goal.status !== "blocked") return;
516671
516697
  this.startupGoalPromptedSessionId = sessionId;
516672
516698
  const choice = await promptStartupResumeGoal(this, goal.objective, startupResumeGoalPromptCopy());
516673
516699
  if (this.aborted || this.session?.id !== sessionId) return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blun-king-cli",
3
- "version": "9.1.417",
3
+ "version": "9.1.419",
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": {
@@ -4702,6 +4702,7 @@ async function handleInbound(event) {
4702
4702
  ...msgId !== void 0 ? { message_id: String(msgId) } : {},
4703
4703
  addressed: String(addressed),
4704
4704
  user: ctx.from?.username ?? String(ctx.from?.id),
4705
+ is_bot: String(ctx.from?.is_bot === true),
4705
4706
  user_id: String(ctx.from?.id),
4706
4707
  ts: (/* @__PURE__ */ new Date((ctx.date ?? 0) * 1e3)).toISOString(),
4707
4708
  ...imagePath !== void 0 ? { image_path: imagePath } : {},