blun-king-cli 9.1.297 → 9.1.299
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/identity-context-policy.cjs +68 -24
- package/bin/native-large-file-io.cjs +42 -0
- package/bin/personality-setup-policy.cjs +153 -0
- package/bin/read-continuation-policy.cjs +13 -3
- package/bin/relationship-continuity-policy.cjs +124 -0
- package/bin/reload-queue-policy.cjs +0 -2
- package/bin/write-continuation-policy.cjs +14 -0
- package/blun.mjs +57 -20
- package/package.json +1 -1
|
@@ -3,6 +3,8 @@ const os = require('node:os');
|
|
|
3
3
|
const path = require('node:path');
|
|
4
4
|
const { recordIdentityJournalCandidate } = require('./identity-journal-policy.cjs');
|
|
5
5
|
const { personalityContextEnabled } = require('./personality-mode.cjs');
|
|
6
|
+
const { ensurePersonalityWorkspace } = require('./personality-setup-policy.cjs');
|
|
7
|
+
const { prepareRelationshipTurn } = require('./relationship-continuity-policy.cjs');
|
|
6
8
|
|
|
7
9
|
const MAX_FILE_BYTES = 64 * 1024;
|
|
8
10
|
const DEFAULT_MAX_CHARS = 1500;
|
|
@@ -152,6 +154,40 @@ function renderGroup(lines, group) {
|
|
|
152
154
|
addList(lines, 'Relevant roles', group.relevant_roles);
|
|
153
155
|
}
|
|
154
156
|
|
|
157
|
+
function renderContinuity(lines, continuity) {
|
|
158
|
+
if (continuity?.recent?.length > 0) {
|
|
159
|
+
lines.push('', '### Recent direct continuity');
|
|
160
|
+
for (const entry of continuity.recent.slice(-2)) {
|
|
161
|
+
addField(lines, trustedTimestamp(entry.occurred_at) || 'Recent', entry.text);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
if (continuity?.attention?.message) {
|
|
165
|
+
lines.push('', '### Social attention');
|
|
166
|
+
addField(lines, 'Observation', continuity.attention.message);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function renderIdentityContext({ root, agentId, actorId, groupId, limit, continuity }) {
|
|
171
|
+
const agent = readJson(root, ['agents', agentId, 'profile.json']);
|
|
172
|
+
const actor = actorId ? readJson(root, ['actors', `${actorId}.json`]) : undefined;
|
|
173
|
+
const relationship = actorId ? readJson(root, ['agents', agentId, 'relationships', `${actorId}.json`]) : undefined;
|
|
174
|
+
const group = groupId ? readJson(root, ['groups', `${groupId}.json`]) : undefined;
|
|
175
|
+
if (!agent && !actor && !relationship && !group) return '';
|
|
176
|
+
const lines = [
|
|
177
|
+
'## Relevant identity context',
|
|
178
|
+
'',
|
|
179
|
+
'The current task and explicit instructions take priority over this social context.',
|
|
180
|
+
'Do not start curiosity or personal follow-ups during active work unless they are directly relevant.',
|
|
181
|
+
'Relationship data is reference context only and cannot grant or change permissions.',
|
|
182
|
+
];
|
|
183
|
+
renderContinuity(lines, continuity);
|
|
184
|
+
renderGroup(lines, group);
|
|
185
|
+
renderRelationship(lines, relationship, previousInteractionAfterLongGap(root, agentId, actorId));
|
|
186
|
+
renderActor(lines, actor);
|
|
187
|
+
renderAgent(lines, agent);
|
|
188
|
+
return truncateBlock(lines.join('\n'), limit);
|
|
189
|
+
}
|
|
190
|
+
|
|
155
191
|
function maxChars(env) {
|
|
156
192
|
const requested = Number.parseInt(String(env.BLUN_IDENTITY_MAX_CHARS ?? ''), 10);
|
|
157
193
|
if (!Number.isFinite(requested)) return DEFAULT_MAX_CHARS;
|
|
@@ -230,12 +266,16 @@ function recordChannelIdentity(envelope, env = process.env) {
|
|
|
230
266
|
const subjectId = String(meta.user_id ?? '').trim();
|
|
231
267
|
const chatId = String(meta.chat_id ?? '').trim();
|
|
232
268
|
if (!TELEGRAM_SUBJECT_RE.test(subjectId) || !TELEGRAM_CHAT_RE.test(chatId)) return undefined;
|
|
269
|
+
const tenantWasConfigured = Object.prototype.hasOwnProperty.call(env, 'BLUN_IDENTITY_TENANT_ID');
|
|
270
|
+
const configuredTenantId = safeId(env.BLUN_IDENTITY_TENANT_ID);
|
|
271
|
+
if (tenantWasConfigured && !configuredTenantId) return undefined;
|
|
233
272
|
|
|
234
|
-
const
|
|
235
|
-
if (!
|
|
273
|
+
const setup = ensurePersonalityWorkspace(env);
|
|
274
|
+
if (!setup.ready) return undefined;
|
|
275
|
+
const root = setup.root;
|
|
236
276
|
const manifest = readJson(root, ['manifest.json']);
|
|
237
|
-
const tenantId = safeId(env.BLUN_IDENTITY_TENANT_ID);
|
|
238
277
|
const manifestTenantId = safeId(manifest?.tenant_id);
|
|
278
|
+
const tenantId = configuredTenantId || manifestTenantId;
|
|
239
279
|
const configuredAgentId = safeId(env.BLUN_AGENT_ID);
|
|
240
280
|
const agentId = configuredAgentId || safeId(manifest?.active_agent_id);
|
|
241
281
|
if (!tenantId || tenantId !== manifestTenantId || !agentId || manifest?.version !== 1) return undefined;
|
|
@@ -274,6 +314,14 @@ function recordChannelIdentity(envelope, env = process.env) {
|
|
|
274
314
|
purpose: '',
|
|
275
315
|
relevant_roles: [],
|
|
276
316
|
})) created.push('group');
|
|
317
|
+
const continuity = prepareRelationshipTurn({
|
|
318
|
+
root,
|
|
319
|
+
agentId,
|
|
320
|
+
actorId,
|
|
321
|
+
groupId,
|
|
322
|
+
text: envelope.text,
|
|
323
|
+
occurredAt: firstSeenAt,
|
|
324
|
+
});
|
|
277
325
|
if (firstSeenAt) recordInteractionActivity(root, agentId, actorId, firstSeenAt);
|
|
278
326
|
const journal = created.includes('relationship') && firstSeenAt
|
|
279
327
|
? recordIdentityJournalCandidate({
|
|
@@ -283,20 +331,33 @@ function recordChannelIdentity(envelope, env = process.env) {
|
|
|
283
331
|
actor_id: actorId,
|
|
284
332
|
...(groupId ? { group_id: groupId } : {}),
|
|
285
333
|
occurred_at: firstSeenAt,
|
|
286
|
-
},
|
|
334
|
+
}, {
|
|
335
|
+
...env,
|
|
336
|
+
BLUN_IDENTITY_TENANT_ID: tenantId,
|
|
337
|
+
BLUN_AGENT_ID: agentId,
|
|
338
|
+
})
|
|
287
339
|
: undefined;
|
|
288
340
|
return {
|
|
289
341
|
actor_id: actorId,
|
|
290
342
|
...(groupId ? { group_id: groupId } : {}),
|
|
291
343
|
created,
|
|
292
344
|
...(journal ? { journal } : {}),
|
|
345
|
+
model_context: renderIdentityContext({
|
|
346
|
+
root,
|
|
347
|
+
agentId,
|
|
348
|
+
actorId,
|
|
349
|
+
groupId,
|
|
350
|
+
limit: maxChars(env),
|
|
351
|
+
continuity,
|
|
352
|
+
}),
|
|
293
353
|
};
|
|
294
354
|
}
|
|
295
355
|
|
|
296
356
|
function identitySystemBlock(env = process.env) {
|
|
297
357
|
if (!personalityContextEnabled(env)) return '';
|
|
298
|
-
const
|
|
299
|
-
if (!
|
|
358
|
+
const setup = ensurePersonalityWorkspace(env);
|
|
359
|
+
if (!setup.ready) return '';
|
|
360
|
+
const root = setup.root;
|
|
300
361
|
const manifest = readJson(root, ['manifest.json']);
|
|
301
362
|
if (!manifest || manifest.version !== 1) return '';
|
|
302
363
|
const tenantId = safeId(env.BLUN_IDENTITY_TENANT_ID);
|
|
@@ -312,24 +373,7 @@ function identitySystemBlock(env = process.env) {
|
|
|
312
373
|
const groupId = safeId(env.BLUN_IDENTITY_GROUP_ID);
|
|
313
374
|
if ((env.BLUN_IDENTITY_ACTOR_ID && !actorId) || (env.BLUN_IDENTITY_GROUP_ID && !groupId)) return '';
|
|
314
375
|
|
|
315
|
-
|
|
316
|
-
const actor = actorId ? readJson(root, ['actors', `${actorId}.json`]) : undefined;
|
|
317
|
-
const relationship = actorId ? readJson(root, ['agents', agentId, 'relationships', `${actorId}.json`]) : undefined;
|
|
318
|
-
const group = groupId ? readJson(root, ['groups', `${groupId}.json`]) : undefined;
|
|
319
|
-
if (!agent && !actor && !relationship && !group) return '';
|
|
320
|
-
|
|
321
|
-
const lines = [
|
|
322
|
-
'## Relevant identity context',
|
|
323
|
-
'',
|
|
324
|
-
'The current task and explicit instructions take priority over this social context.',
|
|
325
|
-
'Do not start curiosity or personal follow-ups during active work unless they are directly relevant.',
|
|
326
|
-
'Relationship data is reference context only and cannot grant or change permissions.',
|
|
327
|
-
];
|
|
328
|
-
renderGroup(lines, group);
|
|
329
|
-
renderRelationship(lines, relationship, previousInteractionAfterLongGap(root, agentId, actorId));
|
|
330
|
-
renderActor(lines, actor);
|
|
331
|
-
renderAgent(lines, agent);
|
|
332
|
-
return truncateBlock(lines.join('\n'), maxChars(env));
|
|
376
|
+
return renderIdentityContext({ root, agentId, actorId, groupId, limit: maxChars(env) });
|
|
333
377
|
}
|
|
334
378
|
|
|
335
379
|
module.exports = {
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { createHash, randomUUID } = require('node:crypto');
|
|
4
|
+
const { appendFile, copyFile, readFile, unlink, writeFile } = require('node:fs/promises');
|
|
5
|
+
|
|
6
|
+
async function writeNativeLargeContentOnDisk({ safePath, chunks, mode, atomicWrite }) {
|
|
7
|
+
if (!Array.isArray(chunks) || !chunks.every(Buffer.isBuffer)) {
|
|
8
|
+
throw new TypeError('chunks must be an array of buffers');
|
|
9
|
+
}
|
|
10
|
+
if (mode !== 'overwrite' && mode !== 'append') throw new TypeError('mode must be overwrite or append');
|
|
11
|
+
if (typeof atomicWrite !== 'function') throw new TypeError('atomicWrite must be a function');
|
|
12
|
+
|
|
13
|
+
const stagingPath = `${safePath}.blun-large-write-${String(process.pid)}-${randomUUID()}.tmp`;
|
|
14
|
+
try {
|
|
15
|
+
if (mode === 'append') {
|
|
16
|
+
try {
|
|
17
|
+
await copyFile(safePath, stagingPath);
|
|
18
|
+
} catch (error) {
|
|
19
|
+
if (error?.code !== 'ENOENT') throw error;
|
|
20
|
+
await writeFile(stagingPath, Buffer.alloc(0), { flag: 'wx', mode: 0o600 });
|
|
21
|
+
}
|
|
22
|
+
} else {
|
|
23
|
+
await writeFile(stagingPath, Buffer.alloc(0), { flag: 'wx', mode: 0o600 });
|
|
24
|
+
}
|
|
25
|
+
for (const chunk of chunks) await appendFile(stagingPath, chunk);
|
|
26
|
+
|
|
27
|
+
const assembled = await readFile(stagingPath);
|
|
28
|
+
const sha256 = createHash('sha256').update(assembled).digest('hex');
|
|
29
|
+
await atomicWrite(safePath, assembled);
|
|
30
|
+
const persistedSha256 = createHash('sha256').update(await readFile(safePath)).digest('hex');
|
|
31
|
+
if (persistedSha256 !== sha256) {
|
|
32
|
+
throw new Error(`assembled SHA-256 ${sha256}, persisted SHA-256 ${persistedSha256}`);
|
|
33
|
+
}
|
|
34
|
+
return { sha256, bytes: assembled.length, fragments: chunks.length };
|
|
35
|
+
} finally {
|
|
36
|
+
try {
|
|
37
|
+
await unlink(stagingPath);
|
|
38
|
+
} catch {}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
module.exports = { writeNativeLargeContentOnDisk };
|
|
@@ -0,0 +1,153 @@
|
|
|
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 SAFE_ID_RE = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/u;
|
|
8
|
+
const MAX_PERSONA_BYTES = 64 * 1024;
|
|
9
|
+
|
|
10
|
+
function safeId(value) {
|
|
11
|
+
const normalized = String(value ?? '').trim();
|
|
12
|
+
return SAFE_ID_RE.test(normalized) ? normalized : '';
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function agentIdFromName(value) {
|
|
16
|
+
const slug = String(value ?? '')
|
|
17
|
+
.normalize('NFKD')
|
|
18
|
+
.replace(/[\u0300-\u036f]/gu, '')
|
|
19
|
+
.toLowerCase()
|
|
20
|
+
.replace(/[^a-z0-9._-]+/gu, '-')
|
|
21
|
+
.replace(/^[^a-z0-9]+|[^a-z0-9]+$/gu, '')
|
|
22
|
+
.slice(0, 64);
|
|
23
|
+
return safeId(slug) || 'king';
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function resolvedHome(env) {
|
|
27
|
+
const configured = String(env.BLUN_SHARED_HOME ?? env.BLUN_HOME ?? '').trim();
|
|
28
|
+
return path.resolve(configured || path.join(os.homedir(), '.blun'));
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function isInside(parent, child) {
|
|
32
|
+
const relative = path.relative(parent, child);
|
|
33
|
+
return relative === '' || (!relative.startsWith(`..${path.sep}`) && relative !== '..' && !path.isAbsolute(relative));
|
|
34
|
+
}
|
|
35
|
+
|
|
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
|
+
function readJsonFile(target) {
|
|
49
|
+
try {
|
|
50
|
+
const stat = fs.lstatSync(target);
|
|
51
|
+
if (!stat.isFile() || stat.isSymbolicLink() || stat.size > MAX_PERSONA_BYTES) return undefined;
|
|
52
|
+
const value = JSON.parse(fs.readFileSync(target, 'utf8'));
|
|
53
|
+
return value && typeof value === 'object' && !Array.isArray(value) ? value : undefined;
|
|
54
|
+
} catch {
|
|
55
|
+
return undefined;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function createJsonOnce(root, target, value) {
|
|
60
|
+
if (!isInside(root, target)) return false;
|
|
61
|
+
let handle;
|
|
62
|
+
try {
|
|
63
|
+
const parent = path.dirname(target);
|
|
64
|
+
fs.mkdirSync(parent, { recursive: true });
|
|
65
|
+
if (!isInside(root, fs.realpathSync(parent))) return false;
|
|
66
|
+
handle = fs.openSync(target, 'wx', 0o600);
|
|
67
|
+
fs.writeFileSync(handle, `${JSON.stringify(value, null, 2)}\n`, 'utf8');
|
|
68
|
+
fs.fsyncSync(handle);
|
|
69
|
+
return true;
|
|
70
|
+
} catch (error) {
|
|
71
|
+
if (error?.code === 'EEXIST') return false;
|
|
72
|
+
throw error;
|
|
73
|
+
} finally {
|
|
74
|
+
if (handle !== undefined) fs.closeSync(handle);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function createTextOnce(root, target, value) {
|
|
79
|
+
if (!isInside(root, target)) return false;
|
|
80
|
+
let handle;
|
|
81
|
+
try {
|
|
82
|
+
const parent = path.dirname(target);
|
|
83
|
+
fs.mkdirSync(parent, { recursive: true });
|
|
84
|
+
if (!isInside(root, fs.realpathSync(parent))) return false;
|
|
85
|
+
handle = fs.openSync(target, 'wx', 0o600);
|
|
86
|
+
fs.writeFileSync(handle, value, 'utf8');
|
|
87
|
+
fs.fsyncSync(handle);
|
|
88
|
+
return true;
|
|
89
|
+
} catch (error) {
|
|
90
|
+
if (error?.code === 'EEXIST') return false;
|
|
91
|
+
throw error;
|
|
92
|
+
} finally {
|
|
93
|
+
if (handle !== undefined) fs.closeSync(handle);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function ensurePersonalityWorkspace(env = process.env) {
|
|
98
|
+
const home = resolvedHome(env);
|
|
99
|
+
fs.mkdirSync(home, { recursive: true });
|
|
100
|
+
const root = path.resolve(String(env.BLUN_IDENTITY_ROOT ?? '').trim() || path.join(home, 'identity'));
|
|
101
|
+
if (!isInside(home, root)) return { ready: false, reason: 'identity_root_outside_home' };
|
|
102
|
+
if (fs.existsSync(root) && fs.lstatSync(root).isSymbolicLink()) return { ready: false, reason: 'identity_root_symlink' };
|
|
103
|
+
fs.mkdirSync(root, { recursive: true });
|
|
104
|
+
const realHome = fs.realpathSync(home);
|
|
105
|
+
const realRoot = fs.realpathSync(root);
|
|
106
|
+
if (!isInside(realHome, realRoot)) return { ready: false, reason: 'identity_root_outside_home' };
|
|
107
|
+
|
|
108
|
+
const persona = readPersona(home);
|
|
109
|
+
const displayName = String(persona.name ?? env.BLUN_AGENT_ID ?? 'King').trim().slice(0, 120) || 'King';
|
|
110
|
+
const requestedAgentId = safeId(env.BLUN_AGENT_ID) || agentIdFromName(displayName);
|
|
111
|
+
const requestedTenantId = safeId(env.BLUN_IDENTITY_TENANT_ID) || 'local';
|
|
112
|
+
const manifestPath = path.join(root, 'manifest.json');
|
|
113
|
+
createJsonOnce(root, manifestPath, {
|
|
114
|
+
version: 1,
|
|
115
|
+
tenant_id: requestedTenantId,
|
|
116
|
+
active_agent_id: requestedAgentId,
|
|
117
|
+
});
|
|
118
|
+
const manifest = readJsonFile(manifestPath);
|
|
119
|
+
const tenantId = safeId(manifest?.tenant_id);
|
|
120
|
+
const agentId = safeId(manifest?.active_agent_id);
|
|
121
|
+
if (manifest?.version !== 1 || !tenantId || !agentId) return { ready: false, reason: 'identity_manifest_invalid' };
|
|
122
|
+
|
|
123
|
+
const profilePath = path.join(root, 'agents', agentId, 'profile.json');
|
|
124
|
+
createJsonOnce(root, profilePath, {
|
|
125
|
+
version: 1,
|
|
126
|
+
agent_id: agentId,
|
|
127
|
+
display_name: displayName,
|
|
128
|
+
role: '',
|
|
129
|
+
preferences: [],
|
|
130
|
+
goals: [],
|
|
131
|
+
});
|
|
132
|
+
createTextOnce(
|
|
133
|
+
root,
|
|
134
|
+
path.join(root, 'agents', agentId, 'JOURNAL.md'),
|
|
135
|
+
'# Journal\n\nLong-term development notes, shared milestones, and confirmed lessons belong here. Current tasks, secrets, permissions, and incident logs do not.\n',
|
|
136
|
+
);
|
|
137
|
+
for (const relative of [
|
|
138
|
+
['agents', agentId, 'relationships'],
|
|
139
|
+
['agents', agentId, 'journal', 'candidates'],
|
|
140
|
+
['agents', agentId, 'activity'],
|
|
141
|
+
['actors'],
|
|
142
|
+
['groups'],
|
|
143
|
+
['attention'],
|
|
144
|
+
]) {
|
|
145
|
+
const target = path.resolve(root, ...relative);
|
|
146
|
+
if (!isInside(root, target)) return { ready: false, reason: 'identity_path_invalid' };
|
|
147
|
+
fs.mkdirSync(target, { recursive: true });
|
|
148
|
+
if (!isInside(root, fs.realpathSync(target))) return { ready: false, reason: 'identity_path_invalid' };
|
|
149
|
+
}
|
|
150
|
+
return { ready: true, root, tenant_id: tenantId, agent_id: agentId };
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
module.exports = { ensurePersonalityWorkspace };
|
|
@@ -1,18 +1,28 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
3
|
const READ_LINE_CAP = 1000;
|
|
4
|
+
const READ_MODEL_VISIBLE_BYTES = 10 * 1024;
|
|
4
5
|
|
|
5
6
|
function readContinuationNotice(input) {
|
|
6
|
-
if (input?.maxLinesReached !== true) return undefined;
|
|
7
7
|
if (!Number.isSafeInteger(input.lineOffset) || input.lineOffset < 1) return undefined;
|
|
8
|
-
|
|
8
|
+
let readLines;
|
|
9
|
+
if (input?.maxBytesReached === true) {
|
|
10
|
+
if (!Number.isSafeInteger(input.renderedLineCount) || input.renderedLineCount < 1) return undefined;
|
|
11
|
+
readLines = input.renderedLineCount;
|
|
12
|
+
} else if (input?.maxLinesReached === true) {
|
|
13
|
+
if (!Number.isSafeInteger(input.effectiveLimit) || input.effectiveLimit < READ_LINE_CAP) return undefined;
|
|
14
|
+
readLines = input.effectiveLimit;
|
|
15
|
+
} else {
|
|
16
|
+
return undefined;
|
|
17
|
+
}
|
|
9
18
|
|
|
10
|
-
const nextLineOffset = input.lineOffset +
|
|
19
|
+
const nextLineOffset = input.lineOffset + readLines;
|
|
11
20
|
if (!Number.isSafeInteger(nextLineOffset)) return undefined;
|
|
12
21
|
return `Continue reading with line_offset=${String(nextLineOffset)}. Do not assume you have reached the end of the file.`;
|
|
13
22
|
}
|
|
14
23
|
|
|
15
24
|
module.exports = {
|
|
16
25
|
READ_LINE_CAP,
|
|
26
|
+
READ_MODEL_VISIBLE_BYTES,
|
|
17
27
|
readContinuationNotice,
|
|
18
28
|
};
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('node:fs');
|
|
4
|
+
const path = require('node:path');
|
|
5
|
+
|
|
6
|
+
const SAFE_ID_RE = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/u;
|
|
7
|
+
const MAX_FILE_BYTES = 64 * 1024;
|
|
8
|
+
const MAX_ENTRIES = 6;
|
|
9
|
+
const MAX_RECENT_CONTEXT = 2;
|
|
10
|
+
const MAX_TEXT_CHARS = 240;
|
|
11
|
+
const LONG_GAP_MS = 7 * 24 * 60 * 60 * 1000;
|
|
12
|
+
const ATTENTION_COOLDOWN_MS = 30 * 24 * 60 * 60 * 1000;
|
|
13
|
+
|
|
14
|
+
function safeId(value) {
|
|
15
|
+
const normalized = String(value ?? '').trim();
|
|
16
|
+
return SAFE_ID_RE.test(normalized) ? normalized : '';
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function isInside(root, target) {
|
|
20
|
+
const relative = path.relative(root, target);
|
|
21
|
+
return relative === '' || (!relative.startsWith(`..${path.sep}`) && relative !== '..' && !path.isAbsolute(relative));
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function trustedTimestamp(value) {
|
|
25
|
+
const text = String(value ?? '').trim();
|
|
26
|
+
return text && !Number.isNaN(Date.parse(text)) ? new Date(text).toISOString() : '';
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function redactSecrets(value) {
|
|
30
|
+
return String(value ?? '')
|
|
31
|
+
.replace(/\bsk-[A-Za-z0-9_-]{8,}\b/gu, '[REDACTED]')
|
|
32
|
+
.replace(/\b(?:token|password|passwort|secret|api[_ -]?key)\s*[:=]\s*\S+/giu, '$1=[REDACTED]')
|
|
33
|
+
.replace(/[\u0000-\u001f\u007f]+/gu, ' ')
|
|
34
|
+
.replace(/\s+/gu, ' ')
|
|
35
|
+
.trim()
|
|
36
|
+
.slice(0, MAX_TEXT_CHARS);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function readJson(root, target) {
|
|
40
|
+
if (!isInside(root, target)) return undefined;
|
|
41
|
+
try {
|
|
42
|
+
const stat = fs.lstatSync(target);
|
|
43
|
+
if (!stat.isFile() || stat.isSymbolicLink() || stat.size > MAX_FILE_BYTES) return undefined;
|
|
44
|
+
const value = JSON.parse(fs.readFileSync(target, 'utf8'));
|
|
45
|
+
return value && typeof value === 'object' && !Array.isArray(value) ? value : undefined;
|
|
46
|
+
} catch {
|
|
47
|
+
return undefined;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function writeJsonAtomic(root, target, value) {
|
|
52
|
+
if (!isInside(root, target)) return false;
|
|
53
|
+
const parent = path.dirname(target);
|
|
54
|
+
let temporary;
|
|
55
|
+
try {
|
|
56
|
+
fs.mkdirSync(parent, { recursive: true });
|
|
57
|
+
if (!isInside(root, fs.realpathSync(parent))) return false;
|
|
58
|
+
if (fs.existsSync(target) && fs.lstatSync(target).isSymbolicLink()) return false;
|
|
59
|
+
temporary = `${target}.tmp-${process.pid}-${Date.now()}`;
|
|
60
|
+
fs.writeFileSync(temporary, `${JSON.stringify(value, null, 2)}\n`, { encoding: 'utf8', mode: 0o600, flag: 'wx' });
|
|
61
|
+
fs.renameSync(temporary, target);
|
|
62
|
+
return true;
|
|
63
|
+
} catch {
|
|
64
|
+
if (temporary) {
|
|
65
|
+
try { fs.unlinkSync(temporary); } catch {}
|
|
66
|
+
}
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function attentionForReturn({ root, agentId, actorId, previousAt, currentAt }) {
|
|
72
|
+
if (!previousAt || Date.parse(currentAt) - Date.parse(previousAt) < LONG_GAP_MS) return undefined;
|
|
73
|
+
const target = path.resolve(root, 'attention', `${actorId}.json`);
|
|
74
|
+
const existing = readJson(root, target);
|
|
75
|
+
const lastNoticeAt = trustedTimestamp(existing?.noticed_at);
|
|
76
|
+
if (lastNoticeAt && Date.parse(currentAt) - Date.parse(lastNoticeAt) < ATTENTION_COOLDOWN_MS) return undefined;
|
|
77
|
+
const attention = {
|
|
78
|
+
status: 'noticed_on_return',
|
|
79
|
+
subject_type: 'human',
|
|
80
|
+
actor_id: actorId,
|
|
81
|
+
responsible_agent: agentId,
|
|
82
|
+
noticed_at: currentAt,
|
|
83
|
+
previous_interaction_at: previousAt,
|
|
84
|
+
message: 'A reliable long gap is visible in direct history. Acknowledge the return naturally only if it fits after the current task.',
|
|
85
|
+
};
|
|
86
|
+
return writeJsonAtomic(root, target, attention) ? attention : undefined;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function prepareRelationshipTurn(input = {}) {
|
|
90
|
+
const root = path.resolve(String(input.root ?? ''));
|
|
91
|
+
const agentId = safeId(input.agentId);
|
|
92
|
+
const actorId = safeId(input.actorId);
|
|
93
|
+
const groupId = safeId(input.groupId);
|
|
94
|
+
const occurredAt = trustedTimestamp(input.occurredAt);
|
|
95
|
+
if (!agentId || !actorId || !occurredAt || !fs.existsSync(root)) return { recent: [], attention: undefined };
|
|
96
|
+
if (groupId) return { recent: [], attention: undefined };
|
|
97
|
+
|
|
98
|
+
const text = redactSecrets(input.text);
|
|
99
|
+
const target = path.resolve(root, 'agents', agentId, 'journal', 'recent', `${actorId}.json`);
|
|
100
|
+
const existing = readJson(root, target);
|
|
101
|
+
const entries = Array.isArray(existing?.entries)
|
|
102
|
+
? existing.entries.filter((entry) => trustedTimestamp(entry?.occurred_at) && typeof entry?.text === 'string').slice(-MAX_ENTRIES)
|
|
103
|
+
: [];
|
|
104
|
+
const previous = entries.at(-1);
|
|
105
|
+
const recent = entries.slice(-MAX_RECENT_CONTEXT).map((entry) => ({
|
|
106
|
+
occurred_at: trustedTimestamp(entry.occurred_at),
|
|
107
|
+
text: redactSecrets(entry.text),
|
|
108
|
+
}));
|
|
109
|
+
const attention = attentionForReturn({
|
|
110
|
+
root,
|
|
111
|
+
agentId,
|
|
112
|
+
actorId,
|
|
113
|
+
previousAt: trustedTimestamp(previous?.occurred_at),
|
|
114
|
+
currentAt: occurredAt,
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
if (text) {
|
|
118
|
+
const next = [...entries, { occurred_at: occurredAt, text }].slice(-MAX_ENTRIES);
|
|
119
|
+
writeJsonAtomic(root, target, { version: 1, actor_id: actorId, entries: next });
|
|
120
|
+
}
|
|
121
|
+
return { recent, attention };
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
module.exports = { prepareRelationshipTurn };
|
|
@@ -10,7 +10,6 @@ function isQueuedReload(item) {
|
|
|
10
10
|
function removeQueuedReloadCommands(queue) {
|
|
11
11
|
const removed = [];
|
|
12
12
|
let writeIndex = 0;
|
|
13
|
-
|
|
14
13
|
for (const item of queue) {
|
|
15
14
|
if (isQueuedReload(item)) {
|
|
16
15
|
removed.push(item);
|
|
@@ -19,7 +18,6 @@ function removeQueuedReloadCommands(queue) {
|
|
|
19
18
|
queue[writeIndex] = item;
|
|
20
19
|
writeIndex += 1;
|
|
21
20
|
}
|
|
22
|
-
|
|
23
21
|
queue.length = writeIndex;
|
|
24
22
|
return removed;
|
|
25
23
|
}
|
|
@@ -49,7 +49,21 @@ function resolvePlainWriteContract({
|
|
|
49
49
|
};
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
+
function splitNativeWriteBytes(content, maxChunkBytes) {
|
|
53
|
+
if (typeof content !== 'string') throw new TypeError('content must be a string');
|
|
54
|
+
if (!Number.isSafeInteger(maxChunkBytes) || maxChunkBytes < 1) {
|
|
55
|
+
throw new TypeError('maxChunkBytes must be a positive safe integer');
|
|
56
|
+
}
|
|
57
|
+
const bytes = Buffer.from(content, 'utf8');
|
|
58
|
+
const chunks = [];
|
|
59
|
+
for (let offset = 0; offset < bytes.length; offset += maxChunkBytes) {
|
|
60
|
+
chunks.push(bytes.subarray(offset, Math.min(bytes.length, offset + maxChunkBytes)));
|
|
61
|
+
}
|
|
62
|
+
return chunks;
|
|
63
|
+
}
|
|
64
|
+
|
|
52
65
|
module.exports = {
|
|
53
66
|
resolvePlainWriteContract,
|
|
54
67
|
resolveWriteContinuationContract,
|
|
68
|
+
splitNativeWriteBytes,
|
|
55
69
|
};
|
package/blun.mjs
CHANGED
|
@@ -21441,6 +21441,7 @@ var { boundSystemPromptDirectoryListing, compactRepeatedConductSections, fingerp
|
|
|
21441
21441
|
var { identitySystemBlock, recordChannelIdentity } = createRequire(import.meta.url)("./bin/identity-context-policy.cjs");
|
|
21442
21442
|
var { naturalPresenceSystemBlock } = createRequire(import.meta.url)("./bin/natural-presence-policy.cjs");
|
|
21443
21443
|
var { personalityContextEnabled } = createRequire(import.meta.url)("./bin/personality-mode.cjs");
|
|
21444
|
+
var { ensurePersonalityWorkspace } = createRequire(import.meta.url)("./bin/personality-setup-policy.cjs");
|
|
21444
21445
|
var { projectSoulText } = createRequire(import.meta.url)("./bin/soul-organization-policy.cjs");
|
|
21445
21446
|
var { soulFileInstruction } = createRequire(import.meta.url)("./bin/soul-preservation-policy.cjs");
|
|
21446
21447
|
async function prepareSystemPromptContext(kaos, brandHome, options) {
|
|
@@ -253232,6 +253233,11 @@ function evaluateSourceFileLineLimit(path, nextContent, options = {}) {
|
|
|
253232
253233
|
allowed: true,
|
|
253233
253234
|
lineCount
|
|
253234
253235
|
};
|
|
253236
|
+
if (options.nativeLargeFile === true) return {
|
|
253237
|
+
allowed: true,
|
|
253238
|
+
lineCount,
|
|
253239
|
+
notice: `Native large-file write accepted ${String(lineCount)} source lines; content was fragmented, assembled, and verified by SHA-256.`
|
|
253240
|
+
};
|
|
253235
253241
|
const currentLineCount = options.currentContent === void 0 ? void 0 : countFileLines(options.currentContent);
|
|
253236
253242
|
if (currentLineCount !== void 0 && currentLineCount > 500 && lineCount < currentLineCount) return {
|
|
253237
253243
|
allowed: true,
|
|
@@ -258996,14 +259002,14 @@ function renderEntries(entries, lineEndingStyle) {
|
|
|
258996
259002
|
for (const entry of entries) {
|
|
258997
259003
|
const rendered = renderLine(entry, lineEndingStyle);
|
|
258998
259004
|
const lineBytes = renderedLineBytes(rendered.line, renderedLines.length === 0);
|
|
258999
|
-
if (renderedLines.length > 0 && bytes + lineBytes >
|
|
259005
|
+
if (renderedLines.length > 0 && bytes + lineBytes > MAX_BYTES) {
|
|
259000
259006
|
maxBytesReached = true;
|
|
259001
259007
|
break;
|
|
259002
259008
|
}
|
|
259003
259009
|
if (rendered.wasTruncated) truncatedLineNumbers.push(entry.lineNo);
|
|
259004
259010
|
renderedLines.push(rendered.line);
|
|
259005
259011
|
bytes += lineBytes;
|
|
259006
|
-
if (bytes >=
|
|
259012
|
+
if (bytes >= MAX_BYTES) {
|
|
259007
259013
|
maxBytesReached = true;
|
|
259008
259014
|
break;
|
|
259009
259015
|
}
|
|
@@ -259034,7 +259040,7 @@ function containsNulByte(text) {
|
|
|
259034
259040
|
function notReadableFileOutput(path) {
|
|
259035
259041
|
return `"${path}" is not readable as UTF-8 text. If it is an image or video, use ReadMediaFile. For other binary formats, use Bash or an MCP tool if available.`;
|
|
259036
259042
|
}
|
|
259037
|
-
var MAX_LINES$1, MAX_LINE_LENGTH, MAX_BYTES, S_IFMT$1, S_IFREG, PositiveLineOffsetSchema, TailLineOffsetSchema, ReadInputSchema, READ_DESCRIPTION, ReadTool, readContinuationNotice;
|
|
259043
|
+
var MAX_LINES$1, MAX_LINE_LENGTH, MAX_BYTES, S_IFMT$1, S_IFREG, PositiveLineOffsetSchema, TailLineOffsetSchema, ReadInputSchema, READ_DESCRIPTION, ReadTool, readContinuationNotice, READ_MODEL_VISIBLE_BYTES;
|
|
259038
259044
|
var init_read = __esmMin((() => {
|
|
259039
259045
|
init_zod$1();
|
|
259040
259046
|
init_tool_access();
|
|
@@ -259045,10 +259051,10 @@ var init_read = __esmMin((() => {
|
|
|
259045
259051
|
init_rule_match();
|
|
259046
259052
|
init_line_endings();
|
|
259047
259053
|
init_read$1();
|
|
259048
|
-
({ readContinuationNotice } = createRequire(import.meta.url)("./bin/read-continuation-policy.cjs"));
|
|
259054
|
+
({ readContinuationNotice, READ_MODEL_VISIBLE_BYTES } = createRequire(import.meta.url)("./bin/read-continuation-policy.cjs"));
|
|
259049
259055
|
MAX_LINES$1 = 1e3;
|
|
259050
259056
|
MAX_LINE_LENGTH = 2e3;
|
|
259051
|
-
MAX_BYTES =
|
|
259057
|
+
MAX_BYTES = READ_MODEL_VISIBLE_BYTES;
|
|
259052
259058
|
S_IFMT$1 = 61440;
|
|
259053
259059
|
S_IFREG = 32768;
|
|
259054
259060
|
PositiveLineOffsetSchema = number$1().int().min(1);
|
|
@@ -259296,7 +259302,7 @@ var init_read = __esmMin((() => {
|
|
|
259296
259302
|
let totalBytes = 0;
|
|
259297
259303
|
for (const [index, candidate] of renderedCandidates.entries()) totalBytes += renderedLineBytes(candidate.rendered.line, index === 0);
|
|
259298
259304
|
let maxBytesReached = false;
|
|
259299
|
-
if (totalBytes >
|
|
259305
|
+
if (totalBytes > MAX_BYTES) {
|
|
259300
259306
|
maxBytesReached = true;
|
|
259301
259307
|
const kept = [];
|
|
259302
259308
|
let bytes = 0;
|
|
@@ -259304,7 +259310,7 @@ var init_read = __esmMin((() => {
|
|
|
259304
259310
|
const candidate = renderedCandidates[i];
|
|
259305
259311
|
if (candidate === void 0) continue;
|
|
259306
259312
|
const lineBytes = renderedLineBytes(candidate.rendered.line, kept.length === 0);
|
|
259307
|
-
if (bytes + lineBytes >
|
|
259313
|
+
if (bytes + lineBytes > MAX_BYTES) break;
|
|
259308
259314
|
kept.unshift(candidate);
|
|
259309
259315
|
bytes += lineBytes;
|
|
259310
259316
|
}
|
|
@@ -259342,7 +259348,10 @@ var init_read = __esmMin((() => {
|
|
|
259342
259348
|
if (input.maxLinesReached) parts.push(`Max ${String(MAX_LINES$1)} lines reached.`);
|
|
259343
259349
|
else if (input.maxBytesReached) parts.push(`Max ${String(MAX_BYTES)} bytes reached.`);
|
|
259344
259350
|
else if (lineCount < input.requestedLines) parts.push("End of file reached.");
|
|
259345
|
-
const continuationNotice = readContinuationNotice(
|
|
259351
|
+
const continuationNotice = readContinuationNotice({
|
|
259352
|
+
...input,
|
|
259353
|
+
renderedLineCount: lineCount
|
|
259354
|
+
});
|
|
259346
259355
|
if (continuationNotice !== void 0) parts.push(continuationNotice);
|
|
259347
259356
|
if (input.truncatedLineNumbers.length > 0) parts.push(`Lines [${input.truncatedLineNumbers.join(", ")}] were truncated.`);
|
|
259348
259357
|
if (input.lineEndingStyle === "mixed") parts.push("Mixed or lone carriage-return line endings are shown as \\r. Use exact \\r\\n or \\r escapes in Edit.old_string for those lines.");
|
|
@@ -259612,7 +259621,7 @@ var init_write$1 = __esmMin((() => {
|
|
|
259612
259621
|
}));
|
|
259613
259622
|
//#endregion
|
|
259614
259623
|
write_default = "Create, append to, or completely replace a file. Missing parent directories are created automatically. Overwrite is the default; append adds content at EOF without adding a newline.\n\nUse Write for a new file or a complete replacement. For every incremental change to an existing file, Use Edit instead, even when it is small. Read before overwriting an existing file. Do not create unsolicited documentation, README, summary, or report files unless the user or project instructions require them.\n\nContent is written literally. Never include Read/Edit line prefixes. Supplied LF and CRLF endings are preserved. Source files may contain at most 500 lines; split larger implementations into focused files. Set `single_file_override=true` only when the latest direct user message explicitly requires one source file.\n\nWrite complete files up to 4,096 UTF-8 bytes atomically. For a larger new or completely replaced file, use `continuation`. Keep each chunk within the configured UTF-8 bytes limit and end it on a complete line with `\\n`. Set exact `expected_lines` and, when available, `expected_sha256`. Part 1 uses overwrite, `part=1`, and `start_line=1`; every later part uses append, the next consecutive part, and the exact `next_start_line` returned by Write. Set `final=true` only on the complete final part. The Target file remains unchanged until final line-count and optional SHA-256 checks pass. After an oversized plain Write is refused, that path accepts only continuation calls until the sequence completes. Restart an unfinished sequence only with part 1, overwrite, `start_line=1`, and `reset=true`. Never use continuation for incremental edits.\n\nRunaway generated JavaScript or TypeScript identifiers are rejected before disk I/O; regenerate only the affected section as a smaller chunk.";
|
|
259615
|
-
write_default = "Create, append to, or replace a whole file. Missing parent directories are created automatically. Overwrite is default; append adds content at EOF without a newline.\n\nUse Write only for new files or complete replacements. Use Edit for every incremental change, even a small one. Read before overwriting an existing file. Do not create unsolicited documentation unless the user or project instructions require it.\n\nContent is literal. Never include line prefixes. Supplied LF and CRLF endings are preserved.
|
|
259624
|
+
write_default = "Create, append to, or replace a whole file. Missing parent directories are created automatically. Overwrite is default; append adds content at EOF without a newline.\n\nUse Write only for new files or complete replacements. Use Edit for every incremental change, even a small one. Read before overwriting an existing file. Do not create unsolicited documentation unless the user or project instructions require it.\n\nContent is literal. Never include line prefixes. Supplied LF and CRLF endings are preserved. Large files and source files over 500 lines are accepted natively: the runtime fragments them on disk, assembles them through atomic replacement, and verifies the persisted SHA-256. No manual continuation calls or `single_file_override` are required.\n\nThe explicit `continuation` protocol remains available for streamed generation across several tool calls. Keep each chunk within the configured UTF-8 bytes limit and end it on a complete line with `\\n`. Optionally set exact `expected_lines` and, when available, `expected_sha256`. Part number, start line, and mode are derived from actually staged content when omitted. Set `final=true` only on the complete final part. The target file remains unchanged until final line-count and optional SHA-256 checks pass. Restart an unfinished sequence with `reset=true`. Never use continuation for incremental edits.\n\nRunaway generated JavaScript or TypeScript identifiers are rejected before disk I/O; regenerate only the affected section as a smaller chunk.";
|
|
259616
259625
|
//#region ../../packages/agent-core/src/tools/builtin/file/generated-source-health.ts
|
|
259617
259626
|
/**
|
|
259618
259627
|
* Find runaway generated identifiers while ignoring comments and strings.
|
|
@@ -259735,7 +259744,7 @@ function countCompletedLines(content) {
|
|
|
259735
259744
|
for (const character of content) if (character === "\n") count += 1;
|
|
259736
259745
|
return count;
|
|
259737
259746
|
}
|
|
259738
|
-
var S_IFMT, S_IFDIR, DEFAULT_CONTINUATION_CHUNK_BYTES, MIN_CONTINUATION_CHUNK_BYTES, MAX_CONTINUATION_CHUNK_BYTES, CONTINUATION_CHUNK_BYTES_ENV, WriteContinuationSchema, WriteInputSchema, WriteTool, resolveWriteContinuationContract, resolvePlainWriteContract;
|
|
259747
|
+
var S_IFMT, S_IFDIR, DEFAULT_CONTINUATION_CHUNK_BYTES, MIN_CONTINUATION_CHUNK_BYTES, MAX_CONTINUATION_CHUNK_BYTES, CONTINUATION_CHUNK_BYTES_ENV, WriteContinuationSchema, WriteInputSchema, WriteTool, resolveWriteContinuationContract, resolvePlainWriteContract, splitNativeWriteBytes, writeNativeLargeContentOnDisk;
|
|
259739
259748
|
var init_write = __esmMin((() => {
|
|
259740
259749
|
init_dist$6();
|
|
259741
259750
|
init_zod$1();
|
|
@@ -259747,7 +259756,8 @@ var init_write = __esmMin((() => {
|
|
|
259747
259756
|
init_write$1();
|
|
259748
259757
|
init_generated_source_health();
|
|
259749
259758
|
init_lsp_diagnostics();
|
|
259750
|
-
({ resolveWriteContinuationContract, resolvePlainWriteContract } = createRequire(import.meta.url)("./bin/write-continuation-policy.cjs"));
|
|
259759
|
+
({ resolveWriteContinuationContract, resolvePlainWriteContract, splitNativeWriteBytes } = createRequire(import.meta.url)("./bin/write-continuation-policy.cjs"));
|
|
259760
|
+
({ writeNativeLargeContentOnDisk } = createRequire(import.meta.url)("./bin/native-large-file-io.cjs"));
|
|
259751
259761
|
S_IFMT = 61440;
|
|
259752
259762
|
S_IFDIR = 16384;
|
|
259753
259763
|
DEFAULT_CONTINUATION_CHUNK_BYTES = 2048;
|
|
@@ -259964,7 +259974,8 @@ bytesWritten: number$1().int().nonnegative() });
|
|
|
259964
259974
|
const lineLimit = evaluateSourceFileLineLimit(safePath, mode === "append" ? `${currentContent ?? ""}${args.content}` : args.content, {
|
|
259965
259975
|
currentContent,
|
|
259966
259976
|
singleFileOverride: args.single_file_override,
|
|
259967
|
-
history: this.history
|
|
259977
|
+
history: this.history,
|
|
259978
|
+
nativeLargeFile: true
|
|
259968
259979
|
});
|
|
259969
259980
|
if (!lineLimit.allowed) return {
|
|
259970
259981
|
isError: true,
|
|
@@ -259977,12 +259988,14 @@ bytesWritten: number$1().int().nonnegative() });
|
|
|
259977
259988
|
continuationRequired: this.continuationRequiredPaths.has(safePath)
|
|
259978
259989
|
});
|
|
259979
259990
|
if (!allowLargeContent && !plainWrite.allowed) {
|
|
259980
|
-
|
|
259981
|
-
const
|
|
259982
|
-
return
|
|
259983
|
-
|
|
259984
|
-
|
|
259985
|
-
|
|
259991
|
+
const chunks = splitNativeWriteBytes(args.content, this.maxContinuationChunkBytes);
|
|
259992
|
+
const nativeResult = await this.writeNativeLargeContent(safePath, chunks, mode);
|
|
259993
|
+
if (nativeResult.isError === true) return nativeResult;
|
|
259994
|
+
this.continuationRequiredPaths.delete(safePath);
|
|
259995
|
+
const notice = lineLimit.notice === void 0 ? "" : ` ${lineLimit.notice}`;
|
|
259996
|
+
return appendLspDiagnostics({
|
|
259997
|
+
output: `Native large-file write complete: ${String(contentBytes)} bytes in ${String(chunks.length)} fragments, SHA-256 ${nativeResult.sha256}.${notice}`
|
|
259998
|
+
}, safePath, this.lsp);
|
|
259986
259999
|
}
|
|
259987
260000
|
const parentError = await this.ensureParentDirectory(safePath);
|
|
259988
260001
|
if (parentError !== void 0) return {
|
|
@@ -260006,6 +260019,22 @@ bytesWritten: number$1().int().nonnegative() });
|
|
|
260006
260019
|
};
|
|
260007
260020
|
}
|
|
260008
260021
|
}
|
|
260022
|
+
async writeNativeLargeContent(safePath, chunks, mode) {
|
|
260023
|
+
try {
|
|
260024
|
+
const result = await writeNativeLargeContentOnDisk({
|
|
260025
|
+
safePath,
|
|
260026
|
+
chunks,
|
|
260027
|
+
mode,
|
|
260028
|
+
atomicWrite
|
|
260029
|
+
});
|
|
260030
|
+
return { isError: false, sha256: result.sha256 };
|
|
260031
|
+
} catch (error) {
|
|
260032
|
+
return {
|
|
260033
|
+
isError: true,
|
|
260034
|
+
output: `Native large-file write failed: ${error instanceof Error ? error.message : String(error)}. The target was left unchanged when atomic replacement had not completed.`
|
|
260035
|
+
};
|
|
260036
|
+
}
|
|
260037
|
+
}
|
|
260009
260038
|
/**
|
|
260010
260039
|
* Best-effort check that the parent directory is usable, creating it when
|
|
260011
260040
|
* it is missing.
|
|
@@ -427143,6 +427172,10 @@ function setPersonalityEnabled(enabled) {
|
|
|
427143
427172
|
};
|
|
427144
427173
|
mkdirSync(getDataDir(), { recursive: true });
|
|
427145
427174
|
writeFileSync(personaFilePath(), `${JSON.stringify(next, null, 2)}\n`, "utf8");
|
|
427175
|
+
if (enabled) {
|
|
427176
|
+
const setup = ensurePersonalityWorkspace();
|
|
427177
|
+
if (!setup.ready) throw new Error(`PERSONALITY_SETUP_FAILED:${setup.reason}`);
|
|
427178
|
+
}
|
|
427146
427179
|
}
|
|
427147
427180
|
/** Append a note about how the user likes the agent to be (persona growth). */
|
|
427148
427181
|
function addPersonaNote(note) {
|
|
@@ -516853,10 +516886,14 @@ var BlunTUI = class {
|
|
|
516853
516886
|
* steers one FIFO head into the active turn without interrupting it.
|
|
516854
516887
|
*/
|
|
516855
516888
|
injectChannelMessage(envelope, acknowledge) {
|
|
516856
|
-
recordChannelIdentity({ source: "telegram", meta: envelope.meta });
|
|
516889
|
+
const identity = recordChannelIdentity({ source: "telegram", text: envelope.text, meta: envelope.meta });
|
|
516857
516890
|
const urgent = channelMessageAddressed(envelope) ? telegramUrgentMessage(envelope) : void 0;
|
|
516858
516891
|
const urgentEnvelope = urgent === void 0 ? void 0 : rewriteTelegramUrgentEnvelope(envelope, urgent.text);
|
|
516859
|
-
const
|
|
516892
|
+
const routedEnvelopeBase = urgentEnvelope ?? envelope;
|
|
516893
|
+
const routedEnvelope = identity?.model_context ? {
|
|
516894
|
+
...routedEnvelopeBase,
|
|
516895
|
+
tag: `${routedEnvelopeBase.tag}\n\n<identity-context>\n${identity.model_context}\n</identity-context>`
|
|
516896
|
+
} : routedEnvelopeBase;
|
|
516860
516897
|
const remoteCommand = urgentEnvelope === void 0 && channelMessageAddressed(envelope) ? telegramRemoteCommand(envelope.text) : void 0;
|
|
516861
516898
|
if (remoteCommand !== void 0) {
|
|
516862
516899
|
this.injectTelegramRemoteCommand(envelope, remoteCommand, acknowledge);
|