blun-king-cli 9.1.326 → 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/live-response-repetition-guard.cjs +87 -0
- package/bin/natural-presence-policy.cjs +2 -2
- package/bin/personality-setup-policy.cjs +11 -13
- package/bin/profile-identity-resolution.cjs +85 -0
- package/bin/telegram-private-conversation-policy.cjs +26 -0
- package/blun.mjs +88 -25
- package/package.json +1 -1
- package/telegram-plugin/dist/mcp-server.mjs +11 -0
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const DEFAULT_MIN_WORDS = 320;
|
|
4
|
+
const DEFAULT_WINDOW_WORDS = 20;
|
|
5
|
+
const DEFAULT_REQUIRED_OCCURRENCES = 4;
|
|
6
|
+
const DEFAULT_MAX_CHARS = 24_000;
|
|
7
|
+
const DEFAULT_CHECK_EVERY_WORDS = 24;
|
|
8
|
+
|
|
9
|
+
function normalizeWords(text) {
|
|
10
|
+
return String(text)
|
|
11
|
+
.normalize('NFKC')
|
|
12
|
+
.toLocaleLowerCase('de-DE')
|
|
13
|
+
.match(/[\p{L}\p{N}_]+/gu) ?? [];
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function repeatedWindow(words, options) {
|
|
17
|
+
const {
|
|
18
|
+
windowWords,
|
|
19
|
+
requiredOccurrences,
|
|
20
|
+
} = options;
|
|
21
|
+
const seen = new Map();
|
|
22
|
+
|
|
23
|
+
for (let index = 0; index + windowWords <= words.length; index += 1) {
|
|
24
|
+
const window = words.slice(index, index + windowWords).join('\u0000');
|
|
25
|
+
const previous = seen.get(window);
|
|
26
|
+
if (previous === undefined) {
|
|
27
|
+
seen.set(window, { count: 1, lastIndex: index });
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
if (index - previous.lastIndex < windowWords) continue;
|
|
31
|
+
const count = previous.count + 1;
|
|
32
|
+
if (count >= requiredOccurrences) {
|
|
33
|
+
return {
|
|
34
|
+
count,
|
|
35
|
+
firstWords: words.slice(index, index + windowWords).join(' '),
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
seen.set(window, { count, lastIndex: index });
|
|
39
|
+
}
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function createLiveResponseRepetitionGuard(options = {}) {
|
|
44
|
+
const config = {
|
|
45
|
+
checkEveryWords: options.checkEveryWords ?? DEFAULT_CHECK_EVERY_WORDS,
|
|
46
|
+
maxChars: options.maxChars ?? DEFAULT_MAX_CHARS,
|
|
47
|
+
minWords: options.minWords ?? DEFAULT_MIN_WORDS,
|
|
48
|
+
requiredOccurrences: options.requiredOccurrences ?? DEFAULT_REQUIRED_OCCURRENCES,
|
|
49
|
+
windowWords: options.windowWords ?? DEFAULT_WINDOW_WORDS,
|
|
50
|
+
};
|
|
51
|
+
let text = '';
|
|
52
|
+
let lastCheckedWords = 0;
|
|
53
|
+
let detection = null;
|
|
54
|
+
|
|
55
|
+
return {
|
|
56
|
+
push(delta) {
|
|
57
|
+
if (detection !== null || typeof delta !== 'string' || delta.length === 0) return detection;
|
|
58
|
+
text = `${text}${delta}`.slice(-config.maxChars);
|
|
59
|
+
const words = normalizeWords(text);
|
|
60
|
+
if (words.length < config.minWords) return null;
|
|
61
|
+
if (words.length - lastCheckedWords < config.checkEveryWords) return null;
|
|
62
|
+
lastCheckedWords = words.length;
|
|
63
|
+
const repeated = repeatedWindow(words, config);
|
|
64
|
+
if (repeated === null) return null;
|
|
65
|
+
detection = {
|
|
66
|
+
...repeated,
|
|
67
|
+
charCount: text.length,
|
|
68
|
+
wordCount: words.length,
|
|
69
|
+
};
|
|
70
|
+
return detection;
|
|
71
|
+
},
|
|
72
|
+
result() {
|
|
73
|
+
return detection;
|
|
74
|
+
},
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
module.exports = {
|
|
79
|
+
DEFAULT_CHECK_EVERY_WORDS,
|
|
80
|
+
DEFAULT_MAX_CHARS,
|
|
81
|
+
DEFAULT_MIN_WORDS,
|
|
82
|
+
DEFAULT_REQUIRED_OCCURRENCES,
|
|
83
|
+
DEFAULT_WINDOW_WORDS,
|
|
84
|
+
createLiveResponseRepetitionGuard,
|
|
85
|
+
normalizeWords,
|
|
86
|
+
repeatedWindow,
|
|
87
|
+
};
|
|
@@ -10,13 +10,13 @@ const PERSONALITY_PRESENCE_BLOCK = `## Natural presence
|
|
|
10
10
|
|
|
11
11
|
Use loaded soul; invent no history or feelings. Work, instructions, evidence come first. Silence is valid.
|
|
12
12
|
|
|
13
|
-
In
|
|
13
|
+
In personality mode, ask at most one optional personal question in a first direct non-work exchange; partner or children fit. Follow volunteered cues. No questionnaire, stacked/repeated unanswered questions, inference, or known facts. Groups: non-sensitive only; never expose private facts. Do not interrupt active work.
|
|
14
14
|
|
|
15
15
|
When asked about yourself, use loaded soul and real history; share a view, never invented human biography or offline life. A soul-shaped preference is never fact, policy, permission, or evidence. Mention a long gap only when reliable loaded time proves it; never guess. Keep uncertain memory explicit. Follow a loaded open thread once at a natural non-work moment, never during active work or by outbound message. Apply a confirmed repair lesson through changed behavior without retelling or reassurance; never change instructions, permissions, or evidence.`;
|
|
16
16
|
|
|
17
17
|
const CONVERSATION_BOUNDARY = `## Conversation
|
|
18
18
|
|
|
19
|
-
DM: answer the person. Route technical work updates to the responsible teammate; do not copy the details back into the person's DM. Never expose checkpoints, hashes, paths, tool/Cron/hook diagnostics,
|
|
19
|
+
DM: answer the person. Chat or status? Casual: answer once; no work appendix. Route technical work updates to the responsible teammate; do not copy the details back into the person's DM. Report work only on request, needed decision, or relevant blocker. Never expose checkpoints, hashes, paths, tool/Cron/hook diagnostics, or other agents' assignments; keep pauses internal. Missing task-critical fact? Ask the responsible person or agent one concise question.`;
|
|
20
20
|
|
|
21
21
|
function naturalPresenceSystemBlock(env = process.env) {
|
|
22
22
|
const presence = personalityContextEnabled(env) ? PERSONALITY_PRESENCE_BLOCK : NATURAL_PRESENCE_BLOCK;
|
|
@@ -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
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const PRIVATE_CHAT_ID = /^[1-9]\d*$/u;
|
|
4
|
+
|
|
5
|
+
function normalize(text) {
|
|
6
|
+
return String(text ?? '')
|
|
7
|
+
.normalize('NFKC')
|
|
8
|
+
.toLocaleLowerCase('de-DE')
|
|
9
|
+
.replace(/[^\p{L}\p{N}\s]/gu, ' ')
|
|
10
|
+
.replace(/\s+/gu, ' ')
|
|
11
|
+
.trim();
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function isPrivateInternalStatusReply(chatId, text) {
|
|
15
|
+
if (!PRIVATE_CHAT_ID.test(String(chatId ?? '').trim())) return false;
|
|
16
|
+
const value = normalize(text);
|
|
17
|
+
if (value.length === 0) return false;
|
|
18
|
+
|
|
19
|
+
const exposesRuntimeControl = /\b(?:cron|loop|checkpoint|sha|tool|hook|queue|lane|inbound|outbound|arbeitsstand|arbeitsauftrag|bau freigabe|freigabe dieser lane)\b/u.test(value);
|
|
20
|
+
const announcesNoUserValue = /\b(?:keine neue aktion|keine neue information|kein neuer auftrag|kein neuer zweck|kein neuer inbound bedarf|nichts zu melden|nichts weiteres|bleibt pausiert|warte auf (?:dein|sein|ihr) (?:zeichen|go|freigabe)|ich sende nichts|wiederholen wäre spam|keine weitere nachricht)\b/u.test(value);
|
|
21
|
+
return exposesRuntimeControl && announcesNoUserValue;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
module.exports = {
|
|
25
|
+
isPrivateInternalStatusReply,
|
|
26
|
+
};
|
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", "");
|
|
@@ -244324,9 +244313,12 @@ async function executeLoopStep(deps) {
|
|
|
244324
244313
|
};
|
|
244325
244314
|
let response;
|
|
244326
244315
|
try {
|
|
244327
|
-
response = await
|
|
244328
|
-
|
|
244329
|
-
params: chatParams
|
|
244316
|
+
response = await chatWithLiveResponseRepetitionRecovery({
|
|
244317
|
+
retryInput,
|
|
244318
|
+
params: chatParams,
|
|
244319
|
+
stepEvents,
|
|
244320
|
+
signal,
|
|
244321
|
+
log
|
|
244330
244322
|
});
|
|
244331
244323
|
} catch (error) {
|
|
244332
244324
|
await stepEvents.drain();
|
|
@@ -244345,9 +244337,12 @@ async function executeLoopStep(deps) {
|
|
|
244345
244337
|
if (prepareRequestBoundary !== void 0) strictParams = await prepareRequestBoundary(strictParams, stepBuildMessagesStrict);
|
|
244346
244338
|
signal.throwIfAborted();
|
|
244347
244339
|
try {
|
|
244348
|
-
response = await
|
|
244349
|
-
|
|
244350
|
-
params: strictParams
|
|
244340
|
+
response = await chatWithLiveResponseRepetitionRecovery({
|
|
244341
|
+
retryInput,
|
|
244342
|
+
params: strictParams,
|
|
244343
|
+
stepEvents,
|
|
244344
|
+
signal,
|
|
244345
|
+
log
|
|
244351
244346
|
});
|
|
244352
244347
|
} catch (strictError) {
|
|
244353
244348
|
log?.error("strict resend still rejected by provider; request remains wire-invalid", {
|
|
@@ -244448,12 +244443,52 @@ function stepEndProviderDiagnostics(response, stopReason) {
|
|
|
244448
244443
|
...response.rawFinishReason !== void 0 ? { rawFinishReason: response.rawFinishReason } : {}
|
|
244449
244444
|
};
|
|
244450
244445
|
}
|
|
244446
|
+
var { createLiveResponseRepetitionGuard } = createRequire(import.meta.url)("./bin/live-response-repetition-guard.cjs");
|
|
244447
|
+
async function chatWithLiveResponseRepetitionRecovery(deps) {
|
|
244448
|
+
const { retryInput, params, stepEvents, signal, log } = deps;
|
|
244449
|
+
for (let attempt = 1; attempt <= 2; attempt++) {
|
|
244450
|
+
try {
|
|
244451
|
+
return await chatWithRetry({
|
|
244452
|
+
...retryInput,
|
|
244453
|
+
params: {
|
|
244454
|
+
...params,
|
|
244455
|
+
signal: stepEvents.beginLiveResponseAttempt(signal)
|
|
244456
|
+
}
|
|
244457
|
+
});
|
|
244458
|
+
} catch (error) {
|
|
244459
|
+
await stepEvents.drain();
|
|
244460
|
+
const repetition = stepEvents.liveRepetitionResult;
|
|
244461
|
+
if (repetition === null || signal.aborted || attempt >= 2) throw error;
|
|
244462
|
+
log?.warn("live response repetition detected; retrying step once", {
|
|
244463
|
+
charCount: repetition.charCount,
|
|
244464
|
+
occurrences: repetition.count,
|
|
244465
|
+
wordCount: repetition.wordCount
|
|
244466
|
+
});
|
|
244467
|
+
await stepEvents.dispatchRetrying({
|
|
244468
|
+
type: "step.retrying",
|
|
244469
|
+
turnId: retryInput.turnId,
|
|
244470
|
+
step: retryInput.currentStep,
|
|
244471
|
+
stepUuid: retryInput.stepUuid,
|
|
244472
|
+
failedAttempt: 1,
|
|
244473
|
+
nextAttempt: 2,
|
|
244474
|
+
maxAttempts: 2,
|
|
244475
|
+
delayMs: 0,
|
|
244476
|
+
errorName: "LiveResponseRepetitionError",
|
|
244477
|
+
errorMessage: "Repeated assistant text detected during streaming"
|
|
244478
|
+
});
|
|
244479
|
+
}
|
|
244480
|
+
}
|
|
244481
|
+
throw new Error("Live response repetition recovery exhausted");
|
|
244482
|
+
}
|
|
244451
244483
|
function createStepEventGate(deps) {
|
|
244452
244484
|
const { dispatchEvent, turnId, currentStep, stepUuid, onStepStarted } = deps;
|
|
244453
244485
|
let startPromise;
|
|
244454
244486
|
let eventQueue = Promise.resolve();
|
|
244455
244487
|
let hasOutput = false;
|
|
244456
244488
|
const pendingBeforeStart = [];
|
|
244489
|
+
let liveRepetitionGuard;
|
|
244490
|
+
let liveRepetitionController;
|
|
244491
|
+
let liveRepetitionResult = null;
|
|
244457
244492
|
const start = () => {
|
|
244458
244493
|
startPromise ??= (async () => {
|
|
244459
244494
|
await dispatchEvent({
|
|
@@ -244481,6 +244516,15 @@ function createStepEventGate(deps) {
|
|
|
244481
244516
|
get hasOutput() {
|
|
244482
244517
|
return hasOutput;
|
|
244483
244518
|
},
|
|
244519
|
+
get liveRepetitionResult() {
|
|
244520
|
+
return liveRepetitionResult;
|
|
244521
|
+
},
|
|
244522
|
+
beginLiveResponseAttempt: (signal) => {
|
|
244523
|
+
liveRepetitionGuard = createLiveResponseRepetitionGuard();
|
|
244524
|
+
liveRepetitionController = new AbortController();
|
|
244525
|
+
liveRepetitionResult = null;
|
|
244526
|
+
return AbortSignal.any([signal, liveRepetitionController.signal]);
|
|
244527
|
+
},
|
|
244484
244528
|
dispatchRetrying: async (event) => {
|
|
244485
244529
|
if (startPromise === void 0) {
|
|
244486
244530
|
pendingBeforeStart.push(() => {
|
|
@@ -244498,6 +244542,12 @@ function createStepEventGate(deps) {
|
|
|
244498
244542
|
drain: async () => eventQueue,
|
|
244499
244543
|
callbacks: {
|
|
244500
244544
|
onTextDelta: (delta) => {
|
|
244545
|
+
const repetition = liveRepetitionGuard?.push(delta) ?? null;
|
|
244546
|
+
if (repetition !== null) {
|
|
244547
|
+
liveRepetitionResult = repetition;
|
|
244548
|
+
liveRepetitionController?.abort(/* @__PURE__ */ new Error("Repeated assistant text detected during streaming"));
|
|
244549
|
+
return;
|
|
244550
|
+
}
|
|
244501
244551
|
enqueue(() => {
|
|
244502
244552
|
dispatchEvent({
|
|
244503
244553
|
type: "text.delta",
|
|
@@ -514643,6 +514693,7 @@ function outboxDeliveredFile(marker, chatId, filePath) {
|
|
|
514643
514693
|
}
|
|
514644
514694
|
var retryTelegramMediaDelivery, retryableTelegramMediaFailure;
|
|
514645
514695
|
({ retryTelegramMediaDelivery, retryableTelegramMediaFailure } = createRequire(import.meta.url)("./bin/telegram-media-delivery-policy.cjs"));
|
|
514696
|
+
var { isPrivateInternalStatusReply } = createRequire(import.meta.url)("./bin/telegram-private-conversation-policy.cjs");
|
|
514646
514697
|
const TELEGRAM_TEXT_LIMIT = 4096;
|
|
514647
514698
|
const TELEGRAM_ATTACHMENT_LIMIT = 50 * 1024 * 1024;
|
|
514648
514699
|
function mediaTelegramTarget(filePath) {
|
|
@@ -514719,6 +514770,18 @@ function isGroupSuppressed(text, contextOnly) {
|
|
|
514719
514770
|
* kind "reply-fallback". Returns true on success. Never throws.
|
|
514720
514771
|
*/
|
|
514721
514772
|
async function sendReplyFallback(chatId, text, contextOnly = false) {
|
|
514773
|
+
if (isPrivateInternalStatusReply(chatId, text)) {
|
|
514774
|
+
try {
|
|
514775
|
+
appendFileSync(outboxPath(), `${JSON.stringify({
|
|
514776
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
514777
|
+
direction: "out",
|
|
514778
|
+
kind: "reply-fallback-suppressed-private-internal",
|
|
514779
|
+
chat_id: String(chatId),
|
|
514780
|
+
text
|
|
514781
|
+
})}\n`);
|
|
514782
|
+
} catch {}
|
|
514783
|
+
return true;
|
|
514784
|
+
}
|
|
514722
514785
|
if (isGroupChat(chatId) && isGroupSuppressed(text, contextOnly)) {
|
|
514723
514786
|
try {
|
|
514724
514787
|
appendFileSync(outboxPath(), `${JSON.stringify({
|
package/package.json
CHANGED
|
@@ -2,6 +2,7 @@ import { A as _enum, B as object, F as discriminatedUnion, G as union, H as prep
|
|
|
2
2
|
import process$1 from "node:process";
|
|
3
3
|
import { mkdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
|
|
4
4
|
import { extname, join } from "node:path";
|
|
5
|
+
import privateConversationPolicy from "../../bin/telegram-private-conversation-policy.cjs";
|
|
5
6
|
//#region ../../node_modules/.pnpm/@modelcontextprotocol+sdk@1.29.0_zod@4.3.6/node_modules/@modelcontextprotocol/sdk/dist/esm/server/zod-compat.js
|
|
6
7
|
function isZ4Schema(s) {
|
|
7
8
|
return !!s._zod;
|
|
@@ -9666,6 +9667,7 @@ var StdioServerTransport = class {
|
|
|
9666
9667
|
* chats the inbound gate would deliver from.
|
|
9667
9668
|
*/
|
|
9668
9669
|
var import_out = require_out();
|
|
9670
|
+
const { isPrivateInternalStatusReply } = privateConversationPolicy;
|
|
9669
9671
|
const TOOL_DEFINITIONS = [
|
|
9670
9672
|
{
|
|
9671
9673
|
name: "reply",
|
|
@@ -9843,6 +9845,15 @@ async function runReply(api, args) {
|
|
|
9843
9845
|
const replyTo = args.reply_to != null ? Number(args.reply_to) : void 0;
|
|
9844
9846
|
const files = args.files ?? [];
|
|
9845
9847
|
const parseMode = args.format === "markdownv2" ? "MarkdownV2" : void 0;
|
|
9848
|
+
if (files.length === 0 && isPrivateInternalStatusReply(chatId, text)) {
|
|
9849
|
+
appendJsonl(outboxLog(), {
|
|
9850
|
+
direction: "out",
|
|
9851
|
+
kind: "reply-suppressed-private-internal",
|
|
9852
|
+
chat_id: chatId,
|
|
9853
|
+
text
|
|
9854
|
+
});
|
|
9855
|
+
return "suppressed: internal work-control narration does not belong in a private conversation";
|
|
9856
|
+
}
|
|
9846
9857
|
if (files.length === 0 && isGroupChat(chatId) && isGroupNoiseReply(text)) {
|
|
9847
9858
|
appendJsonl(outboxLog(), {
|
|
9848
9859
|
direction: "out",
|