blun-king-cli 9.1.328 → 9.1.329

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.
@@ -455,7 +455,7 @@ function recordChannelIdentity(envelope, env = process.env) {
455
455
  const manifestTenantId = safeId(manifest?.tenant_id);
456
456
  const tenantId = configuredTenantId || manifestTenantId;
457
457
  const configuredAgentId = safeId(env.BLUN_AGENT_ID);
458
- const agentId = configuredAgentId || safeId(manifest?.active_agent_id);
458
+ const agentId = configuredAgentId || safeId(setup.agent_id);
459
459
  if (!tenantId || tenantId !== manifestTenantId || !agentId || manifest?.version !== 1) return undefined;
460
460
 
461
461
  const actorId = `telegram-${subjectId}`;
@@ -575,7 +575,7 @@ function identitySystemBlock(env = process.env) {
575
575
 
576
576
  const configuredAgentId = safeId(env.BLUN_AGENT_ID);
577
577
  if (env.BLUN_AGENT_ID && !configuredAgentId) return '';
578
- const agentId = configuredAgentId || safeId(manifest.active_agent_id);
578
+ const agentId = configuredAgentId || safeId(setup.agent_id);
579
579
  if (!agentId) return '';
580
580
  const actorId = safeId(env.BLUN_IDENTITY_ACTOR_ID);
581
581
  const groupId = safeId(env.BLUN_IDENTITY_GROUP_ID);
@@ -6,6 +6,7 @@ const os = require('node:os');
6
6
  const path = require('node:path');
7
7
 
8
8
  const { ensurePersonalityWorkspace } = require('./personality-setup-policy.cjs');
9
+ const { readProfilePersona } = require('./profile-identity-resolution.cjs');
9
10
 
10
11
  const MAX_PERSONA_BYTES = 64 * 1024;
11
12
 
@@ -14,7 +15,7 @@ function fail(code, detail = '') {
14
15
  }
15
16
 
16
17
  function resolvedHome(env) {
17
- const configured = String(env.BLUN_SHARED_HOME ?? env.BLUN_HOME ?? '').trim();
18
+ const configured = String(env.BLUN_HOME ?? env.BLUN_SHARED_HOME ?? '').trim();
18
19
  return path.resolve(configured || path.join(os.homedir(), '.blun'));
19
20
  }
20
21
 
@@ -3,6 +3,7 @@
3
3
  const fs = require('node:fs');
4
4
  const os = require('node:os');
5
5
  const path = require('node:path');
6
+ const { identityFileCandidates } = require('./profile-identity-resolution.cjs');
6
7
 
7
8
  const MAX_PERSONA_BYTES = 64 * 1024;
8
9
  const INVALID_PERSONALITY_CHOICE = Symbol('invalid-personality-choice');
@@ -13,19 +14,22 @@ function resolvedHome(env) {
13
14
  }
14
15
 
15
16
  function persistedPersonalityChoice(env) {
16
- const personaPath = path.join(resolvedHome(env), 'persona.json');
17
- try {
18
- const stat = fs.lstatSync(personaPath);
19
- if (!stat.isFile() || stat.isSymbolicLink() || stat.size > MAX_PERSONA_BYTES) return INVALID_PERSONALITY_CHOICE;
20
- const persona = JSON.parse(fs.readFileSync(personaPath, 'utf8'));
21
- if (!persona || typeof persona !== 'object' || Array.isArray(persona)) return INVALID_PERSONALITY_CHOICE;
22
- if (!Object.prototype.hasOwnProperty.call(persona, 'personalityEnabled')) return undefined;
23
- return typeof persona.personalityEnabled === 'boolean'
24
- ? persona.personalityEnabled
25
- : INVALID_PERSONALITY_CHOICE;
26
- } catch (error) {
27
- return error?.code === 'ENOENT' ? undefined : INVALID_PERSONALITY_CHOICE;
17
+ const candidates = identityFileCandidates('persona.json', env);
18
+ for (const personaPath of candidates) {
19
+ try {
20
+ const stat = fs.lstatSync(personaPath);
21
+ if (!stat.isFile() || stat.isSymbolicLink() || stat.size > MAX_PERSONA_BYTES) return INVALID_PERSONALITY_CHOICE;
22
+ const persona = JSON.parse(fs.readFileSync(personaPath, 'utf8'));
23
+ if (!persona || typeof persona !== 'object' || Array.isArray(persona)) return INVALID_PERSONALITY_CHOICE;
24
+ if (!Object.prototype.hasOwnProperty.call(persona, 'personalityEnabled')) return undefined;
25
+ return typeof persona.personalityEnabled === 'boolean'
26
+ ? persona.personalityEnabled
27
+ : INVALID_PERSONALITY_CHOICE;
28
+ } catch (error) {
29
+ if (error?.code !== 'ENOENT') return INVALID_PERSONALITY_CHOICE;
30
+ }
28
31
  }
32
+ return undefined;
29
33
  }
30
34
 
31
35
  function personalityContextEnabled(env = process.env) {
@@ -96,20 +96,27 @@ function ensurePersonalityWorkspace(env = process.env) {
96
96
  const realRoot = fs.realpathSync(root);
97
97
  if (!isInside(realHome, realRoot)) return { ready: false, reason: 'identity_root_outside_home' };
98
98
 
99
- const persona = readProfilePersona(env).persona || {};
99
+ const personaResult = readProfilePersona(env);
100
+ const persona = personaResult.persona || {};
100
101
  const displayName = String(persona.name ?? env.BLUN_AGENT_ID ?? 'King').trim().slice(0, 120) || 'King';
101
- const requestedAgentId = safeId(env.BLUN_AGENT_ID) || agentIdFromName(displayName);
102
- const requestedTenantId = safeId(env.BLUN_IDENTITY_TENANT_ID) || 'local';
102
+ const configuredAgentId = safeId(env.BLUN_AGENT_ID);
103
+ const configuredTenantId = safeId(env.BLUN_IDENTITY_TENANT_ID);
104
+ const initialAgentId = configuredAgentId || (personaResult.persona ? agentIdFromName(displayName) : 'king');
105
+ const initialTenantId = configuredTenantId || 'local';
103
106
  const manifestPath = path.join(root, 'manifest.json');
104
107
  createJsonOnce(root, manifestPath, {
105
108
  version: 1,
106
- tenant_id: requestedTenantId,
107
- active_agent_id: requestedAgentId,
109
+ tenant_id: initialTenantId,
110
+ active_agent_id: initialAgentId,
108
111
  });
109
112
  const manifest = readJsonFile(manifestPath);
110
113
  const tenantId = safeId(manifest?.tenant_id);
111
- const agentId = safeId(manifest?.active_agent_id);
112
- if (manifest?.version !== 1 || !tenantId || !agentId) return { ready: false, reason: 'identity_manifest_invalid' };
114
+ if (manifest?.version !== 1 || !tenantId || (configuredTenantId && tenantId !== configuredTenantId)) {
115
+ return { ready: false, reason: 'identity_manifest_invalid' };
116
+ }
117
+ const agentId = configuredAgentId
118
+ || (personaResult.persona ? agentIdFromName(displayName) : safeId(manifest.active_agent_id))
119
+ || 'king';
113
120
 
114
121
  const profilePath = path.join(root, 'agents', agentId, 'profile.json');
115
122
  createJsonOnce(root, profilePath, {
@@ -125,10 +132,14 @@ function ensurePersonalityWorkspace(env = process.env) {
125
132
  path.join(root, 'agents', agentId, 'JOURNAL.md'),
126
133
  '# Journal\n\nLong-term development notes, shared milestones, and confirmed lessons belong here. Current tasks, secrets, permissions, and incident logs do not.\n',
127
134
  );
128
- if (!resolveSoulFile(env).text) {
135
+ const resolvedSoul = resolveSoulFile(env);
136
+ const profileSoulPath = path.join(profileHome, 'SOUL.md');
137
+ if (resolvedSoul.text && path.resolve(resolvedSoul.path) !== path.resolve(profileSoulPath)) {
138
+ createTextOnce(profileHome, profileSoulPath, `${resolvedSoul.text}\n`);
139
+ } else if (!resolvedSoul.text) {
129
140
  createTextOnce(
130
141
  profileHome,
131
- path.join(profileHome, 'SOUL.md'),
142
+ profileSoulPath,
132
143
  `# ${displayName}\n\nIch bin ${displayName}, der persoenliche BLUN-Agent dieses Profils. Meine eigene Stimme, Vorlieben und gewachsene gemeinsame Geschichte werden hier behutsam weiterentwickelt. Bestaetigte Beziehungen gehoeren in den Beziehungsgraphen; Auftraege, Rechte, Secrets und Incident-Logs gehoeren nicht in meine Seele.\n`,
133
144
  );
134
145
  }
@@ -62,6 +62,21 @@ function readProfilePersona(env = process.env, options = {}) {
62
62
  };
63
63
  }
64
64
 
65
+ function legacySoulNames(env, persona) {
66
+ const names = [];
67
+ for (const value of [env.BLUN_AGENT_ID, persona?.name, env.BLUN_PROFILE]) {
68
+ const slug = String(value || '')
69
+ .normalize('NFKD')
70
+ .replace(/[\u0300-\u036f]/gu, '')
71
+ .toLowerCase()
72
+ .replace(/[^a-z0-9._-]+/gu, '-')
73
+ .replace(/^[^a-z0-9]+|[^a-z0-9]+$/gu, '')
74
+ .slice(0, 64);
75
+ if (slug && slug !== 'default' && !names.includes(`_${slug}_soul.md`)) names.push(`_${slug}_soul.md`);
76
+ }
77
+ return names;
78
+ }
79
+
65
80
  function resolveSoulFile(env = process.env, options = {}) {
66
81
  const fsImpl = options.fsImpl || fs;
67
82
  const explicit = String(env.BLUN_SOUL_PATH || '').trim();
@@ -69,7 +84,24 @@ function resolveSoulFile(env = process.env, options = {}) {
69
84
  const target = path.resolve(explicit);
70
85
  return { path: target, text: readSafeText(target, fsImpl), explicit: true };
71
86
  }
72
- const candidates = identityFileCandidates('SOUL.md', env, options.homeDir || os.homedir());
87
+ const homeDir = options.homeDir || os.homedir();
88
+ const profileHome = String(env.BLUN_HOME || '').trim();
89
+ const sharedHome = String(env.BLUN_SHARED_HOME || '').trim();
90
+ const profileName = String(env.BLUN_PROFILE || '').trim().toLowerCase();
91
+ const isNamedProfile = profileName && profileName !== 'default';
92
+ const persona = readProfilePersona(env, options).persona;
93
+ const legacyRoots = uniquePaths([
94
+ sharedHome,
95
+ homeDir,
96
+ profileHome && path.dirname(profileHome),
97
+ ]);
98
+ const legacyCandidates = legacySoulNames(env, persona)
99
+ .flatMap((filename) => legacyRoots.map((root) => path.join(root, filename)));
100
+ const candidates = uniquePaths([
101
+ profileHome && path.join(profileHome, 'SOUL.md'),
102
+ ...legacyCandidates,
103
+ ...(!isNamedProfile ? identityFileCandidates('SOUL.md', env, homeDir).slice(1) : []),
104
+ ]);
73
105
  for (const target of candidates) {
74
106
  const text = readSafeText(target, fsImpl);
75
107
  if (text) return { path: target, text, explicit: false };
@@ -101,7 +101,10 @@ function recordExplicitRelationshipCandidate(input = {}) {
101
101
  if (!fs.lstatSync(root).isDirectory() || fs.lstatSync(root).isSymbolicLink()) return undefined;
102
102
  const realRoot = fs.realpathSync(root);
103
103
  const manifest = readManifest(realRoot);
104
- if (manifest?.version !== 1 || manifest.tenant_id !== tenantId || manifest.active_agent_id !== agentId) return undefined;
104
+ const agentRoot = path.join(realRoot, 'agents', agentId);
105
+ if (manifest?.version !== 1 || manifest.tenant_id !== tenantId) return undefined;
106
+ const agentStat = fs.lstatSync(agentRoot);
107
+ if (!agentStat.isDirectory() || agentStat.isSymbolicLink()) return undefined;
105
108
  // The timestamp keeps later confirmations or contradictions as separate history,
106
109
  // while an identical channel event remains idempotent on retry.
107
110
  const identity = [tenantId, agentId, actorId, occurredAt, explicit.kind, explicit.value].join('\0');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blun-king-cli",
3
- "version": "9.1.328",
3
+ "version": "9.1.329",
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": {