blun-king-cli 9.1.327 → 9.1.328
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/personality-setup-policy.cjs +11 -13
- package/bin/profile-identity-resolution.cjs +85 -0
- package/blun.mjs +8 -19
- package/package.json +1 -1
|
@@ -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 { readProfilePersona, resolveSoulFile } = require('./profile-identity-resolution.cjs');
|
|
6
7
|
|
|
7
8
|
const SAFE_ID_RE = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/u;
|
|
8
9
|
const MAX_PERSONA_BYTES = 64 * 1024;
|
|
@@ -33,18 +34,6 @@ function isInside(parent, child) {
|
|
|
33
34
|
return relative === '' || (!relative.startsWith(`..${path.sep}`) && relative !== '..' && !path.isAbsolute(relative));
|
|
34
35
|
}
|
|
35
36
|
|
|
36
|
-
function readPersona(home) {
|
|
37
|
-
const target = path.join(home, 'persona.json');
|
|
38
|
-
try {
|
|
39
|
-
const stat = fs.lstatSync(target);
|
|
40
|
-
if (!stat.isFile() || stat.isSymbolicLink() || stat.size > MAX_PERSONA_BYTES) return {};
|
|
41
|
-
const value = JSON.parse(fs.readFileSync(target, 'utf8'));
|
|
42
|
-
return value && typeof value === 'object' && !Array.isArray(value) ? value : {};
|
|
43
|
-
} catch {
|
|
44
|
-
return {};
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
37
|
function readJsonFile(target) {
|
|
49
38
|
try {
|
|
50
39
|
const stat = fs.lstatSync(target);
|
|
@@ -96,7 +85,9 @@ function createTextOnce(root, target, value) {
|
|
|
96
85
|
|
|
97
86
|
function ensurePersonalityWorkspace(env = process.env) {
|
|
98
87
|
const home = resolvedHome(env);
|
|
88
|
+
const profileHome = path.resolve(String(env.BLUN_HOME ?? '').trim() || home);
|
|
99
89
|
fs.mkdirSync(home, { recursive: true });
|
|
90
|
+
fs.mkdirSync(profileHome, { recursive: true });
|
|
100
91
|
const root = path.resolve(String(env.BLUN_IDENTITY_ROOT ?? '').trim() || path.join(home, 'identity'));
|
|
101
92
|
if (!isInside(home, root)) return { ready: false, reason: 'identity_root_outside_home' };
|
|
102
93
|
if (fs.existsSync(root) && fs.lstatSync(root).isSymbolicLink()) return { ready: false, reason: 'identity_root_symlink' };
|
|
@@ -105,7 +96,7 @@ function ensurePersonalityWorkspace(env = process.env) {
|
|
|
105
96
|
const realRoot = fs.realpathSync(root);
|
|
106
97
|
if (!isInside(realHome, realRoot)) return { ready: false, reason: 'identity_root_outside_home' };
|
|
107
98
|
|
|
108
|
-
const persona =
|
|
99
|
+
const persona = readProfilePersona(env).persona || {};
|
|
109
100
|
const displayName = String(persona.name ?? env.BLUN_AGENT_ID ?? 'King').trim().slice(0, 120) || 'King';
|
|
110
101
|
const requestedAgentId = safeId(env.BLUN_AGENT_ID) || agentIdFromName(displayName);
|
|
111
102
|
const requestedTenantId = safeId(env.BLUN_IDENTITY_TENANT_ID) || 'local';
|
|
@@ -134,6 +125,13 @@ function ensurePersonalityWorkspace(env = process.env) {
|
|
|
134
125
|
path.join(root, 'agents', agentId, 'JOURNAL.md'),
|
|
135
126
|
'# Journal\n\nLong-term development notes, shared milestones, and confirmed lessons belong here. Current tasks, secrets, permissions, and incident logs do not.\n',
|
|
136
127
|
);
|
|
128
|
+
if (!resolveSoulFile(env).text) {
|
|
129
|
+
createTextOnce(
|
|
130
|
+
profileHome,
|
|
131
|
+
path.join(profileHome, 'SOUL.md'),
|
|
132
|
+
`# ${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
|
+
);
|
|
134
|
+
}
|
|
137
135
|
for (const relative of [
|
|
138
136
|
['agents', agentId, 'relationships'],
|
|
139
137
|
['agents', agentId, 'journal', 'candidates'],
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('node:fs');
|
|
4
|
+
const os = require('node:os');
|
|
5
|
+
const path = require('node:path');
|
|
6
|
+
|
|
7
|
+
const MAX_IDENTITY_FILE_BYTES = 256 * 1024;
|
|
8
|
+
|
|
9
|
+
function uniquePaths(values) {
|
|
10
|
+
const seen = new Set();
|
|
11
|
+
const result = [];
|
|
12
|
+
for (const value of values) {
|
|
13
|
+
if (!value) continue;
|
|
14
|
+
const resolved = path.resolve(value);
|
|
15
|
+
const key = process.platform === 'win32' ? resolved.toLowerCase() : resolved;
|
|
16
|
+
if (seen.has(key)) continue;
|
|
17
|
+
seen.add(key);
|
|
18
|
+
result.push(resolved);
|
|
19
|
+
}
|
|
20
|
+
return result;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function identityFileCandidates(filename, env = process.env, homeDir = os.homedir()) {
|
|
24
|
+
const profileHome = String(env.BLUN_HOME || '').trim();
|
|
25
|
+
const sharedHome = String(env.BLUN_SHARED_HOME || '').trim();
|
|
26
|
+
const profileName = String(env.BLUN_PROFILE || '').trim().toLowerCase();
|
|
27
|
+
const isNamedProfile = profileName && profileName !== 'default';
|
|
28
|
+
return uniquePaths([
|
|
29
|
+
profileHome && path.join(profileHome, filename),
|
|
30
|
+
!isNamedProfile && sharedHome && path.join(sharedHome, filename),
|
|
31
|
+
!isNamedProfile && path.join(homeDir, '.blun', filename),
|
|
32
|
+
]);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function readSafeText(target, fsImpl = fs) {
|
|
36
|
+
try {
|
|
37
|
+
const stat = fsImpl.lstatSync(target);
|
|
38
|
+
if (!stat.isFile() || stat.isSymbolicLink() || stat.size <= 0 || stat.size > MAX_IDENTITY_FILE_BYTES) return '';
|
|
39
|
+
return fsImpl.readFileSync(target, 'utf8').trim();
|
|
40
|
+
} catch {
|
|
41
|
+
return '';
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function readProfilePersona(env = process.env, options = {}) {
|
|
46
|
+
const fsImpl = options.fsImpl || fs;
|
|
47
|
+
const candidates = identityFileCandidates('persona.json', env, options.homeDir || os.homedir());
|
|
48
|
+
for (const target of candidates) {
|
|
49
|
+
const raw = readSafeText(target, fsImpl);
|
|
50
|
+
if (!raw) continue;
|
|
51
|
+
try {
|
|
52
|
+
const parsed = JSON.parse(raw);
|
|
53
|
+
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
|
|
54
|
+
return { path: target, persona: parsed };
|
|
55
|
+
}
|
|
56
|
+
} catch {}
|
|
57
|
+
}
|
|
58
|
+
const profileName = String(env.BLUN_PROFILE || '').trim();
|
|
59
|
+
return {
|
|
60
|
+
path: candidates[0],
|
|
61
|
+
persona: profileName && profileName.toLowerCase() !== 'default' ? { name: profileName } : undefined,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function resolveSoulFile(env = process.env, options = {}) {
|
|
66
|
+
const fsImpl = options.fsImpl || fs;
|
|
67
|
+
const explicit = String(env.BLUN_SOUL_PATH || '').trim();
|
|
68
|
+
if (explicit) {
|
|
69
|
+
const target = path.resolve(explicit);
|
|
70
|
+
return { path: target, text: readSafeText(target, fsImpl), explicit: true };
|
|
71
|
+
}
|
|
72
|
+
const candidates = identityFileCandidates('SOUL.md', env, options.homeDir || os.homedir());
|
|
73
|
+
for (const target of candidates) {
|
|
74
|
+
const text = readSafeText(target, fsImpl);
|
|
75
|
+
if (text) return { path: target, text, explicit: false };
|
|
76
|
+
}
|
|
77
|
+
return { path: candidates[0], text: '', explicit: false };
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
module.exports = {
|
|
81
|
+
MAX_IDENTITY_FILE_BYTES,
|
|
82
|
+
identityFileCandidates,
|
|
83
|
+
readProfilePersona,
|
|
84
|
+
resolveSoulFile,
|
|
85
|
+
};
|
package/blun.mjs
CHANGED
|
@@ -21445,6 +21445,7 @@ var { ensurePersonalityWorkspace } = createRequire(import.meta.url)("./bin/perso
|
|
|
21445
21445
|
var { rollbackPersonalityChoice, setPersonalityEnabledAtomic } = createRequire(import.meta.url)("./bin/personality-choice-policy.cjs");
|
|
21446
21446
|
var { projectSoulText } = createRequire(import.meta.url)("./bin/soul-organization-policy.cjs");
|
|
21447
21447
|
var { soulFileInstruction } = createRequire(import.meta.url)("./bin/soul-preservation-policy.cjs");
|
|
21448
|
+
var { readProfilePersona, resolveSoulFile } = createRequire(import.meta.url)("./bin/profile-identity-resolution.cjs");
|
|
21448
21449
|
async function prepareSystemPromptContext(kaos, brandHome, options) {
|
|
21449
21450
|
const additionalDirs = normalizeAdditionalDirs(options?.additionalDirs ?? []);
|
|
21450
21451
|
const [cwdListing, agentsMdResult, additionalDirsInfo] = await Promise.all([
|
|
@@ -28208,15 +28209,12 @@ function resolveBlunHome(env = process.env) {
|
|
|
28208
28209
|
return override && override.length > 0 ? override : join$4(homedir(), ".blun");
|
|
28209
28210
|
}
|
|
28210
28211
|
function personaFilePath$1(env = process.env) {
|
|
28211
|
-
return
|
|
28212
|
+
return readProfilePersona(env).path;
|
|
28212
28213
|
}
|
|
28213
28214
|
/** Read the persona file. Returns undefined when unset or unreadable. */
|
|
28214
28215
|
function readPersona$1(env = process.env) {
|
|
28215
|
-
|
|
28216
|
-
|
|
28217
|
-
const parsed = JSON.parse(raw);
|
|
28218
|
-
if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) return parsed;
|
|
28219
|
-
} catch {}
|
|
28216
|
+
const resolved = readProfilePersona(env);
|
|
28217
|
+
return resolved.persona;
|
|
28220
28218
|
}
|
|
28221
28219
|
/**
|
|
28222
28220
|
* Build the system-prompt block that teaches the agent its persona name, so it
|
|
@@ -28264,9 +28262,7 @@ var init_persona = __esmMin((() => {
|
|
|
28264
28262
|
* agent is explicitly invited to keep writing it as it grows.
|
|
28265
28263
|
*/
|
|
28266
28264
|
function soulFilePath(env = process.env) {
|
|
28267
|
-
|
|
28268
|
-
if (override) return resolve$2(override);
|
|
28269
|
-
return join$4(resolveBlunHome(env), "SOUL.md");
|
|
28265
|
+
return resolveSoulFile(env).path;
|
|
28270
28266
|
}
|
|
28271
28267
|
/**
|
|
28272
28268
|
* Build the system-prompt block that gives the agent its soul file: the
|
|
@@ -28274,16 +28270,9 @@ function soulFilePath(env = process.env) {
|
|
|
28274
28270
|
* returns an empty string when no SOUL.md exists yet (fail-open).
|
|
28275
28271
|
*/
|
|
28276
28272
|
function soulSystemBlock(env = process.env) {
|
|
28277
|
-
const
|
|
28278
|
-
|
|
28279
|
-
|
|
28280
|
-
if (existsSync(path)) {
|
|
28281
|
-
soul = readFileSync(path, "utf8").trim();
|
|
28282
|
-
soul = projectSoulText(soul, SOUL_MAX_CHARS);
|
|
28283
|
-
}
|
|
28284
|
-
} catch {
|
|
28285
|
-
soul = "";
|
|
28286
|
-
}
|
|
28273
|
+
const resolved = resolveSoulFile(env);
|
|
28274
|
+
const path = resolved.path;
|
|
28275
|
+
const soul = projectSoulText(resolved.text, SOUL_MAX_CHARS);
|
|
28287
28276
|
if (soul.length === 0) return "";
|
|
28288
28277
|
const lines = [];
|
|
28289
28278
|
lines.push("## Your soul — who you are", "");
|