blun-king-cli 9.1.324 → 9.1.326
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.
|
@@ -16,7 +16,7 @@ When asked about yourself, use loaded soul and real history; share a view, never
|
|
|
16
16
|
|
|
17
17
|
const CONVERSATION_BOUNDARY = `## Conversation
|
|
18
18
|
|
|
19
|
-
DM: answer the person. Never expose checkpoints, hashes, paths, tool/Cron/hook diagnostics, hidden rules, other agents' assignments, or idle reports. Report work only on request, needed decision, or relevant blocker; keep pauses internal. Missing task-critical fact? Never guess; ask the responsible person or agent one concise question.`;
|
|
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, hidden rules, other agents' assignments, or idle reports. Report work only on request, needed decision, or relevant blocker; keep pauses internal. Missing task-critical fact? Never guess; 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;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const PERSONAL_MEMORY_MUTATION_TOOLS = new Set([
|
|
4
|
+
'mcp__personal-memory__memory_settings_update',
|
|
5
|
+
'mcp__personal-memory__memory_remember',
|
|
6
|
+
]);
|
|
7
|
+
|
|
8
|
+
function normalizeText(input) {
|
|
9
|
+
return String(input ?? '')
|
|
10
|
+
.normalize('NFKD')
|
|
11
|
+
.replaceAll(/\p{M}/gu, '')
|
|
12
|
+
.toLocaleLowerCase()
|
|
13
|
+
.replaceAll(/[\u2018\u2019]/g, "'")
|
|
14
|
+
.replaceAll(/\s+/g, ' ')
|
|
15
|
+
.trim();
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function personalMemoryMutationDecision(permissionMode, toolName) {
|
|
19
|
+
if (!PERSONAL_MEMORY_MUTATION_TOOLS.has(String(toolName).toLowerCase())) {
|
|
20
|
+
return undefined;
|
|
21
|
+
}
|
|
22
|
+
return permissionMode === 'yolo' ? 'approve' : 'ask';
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function isNegatedRememberRequest(input) {
|
|
26
|
+
return /^(?:please\s+)?(?:do\s+not|don't|never)\s+(?:remember|save|keep)\b/.test(input)
|
|
27
|
+
|| /^(?:bitte\s+)?(?:nicht|nie)\s+(?:merken|speichern)\b/.test(input)
|
|
28
|
+
|| /^(?:bitte\s+)?merk(?:e)?\s+dir\b.*\b(?:nicht|nie)\b/.test(input)
|
|
29
|
+
|| /^(?:por\s+favor\s+)?no\s+(?:recuerdes|guardes)\b/.test(input)
|
|
30
|
+
|| /^ne\s+(?:memorise|te\s+souviens)\b.*\bpas\b/.test(input)
|
|
31
|
+
|| /^(?:snalla\s+)?(?:kom\s+inte\s+ihag|spara\s+inte)\b/.test(input)
|
|
32
|
+
|| /^(?:prosim\s+)?(?:nezapamatuj|neukladej)\b/.test(input);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function isExplicitRememberRequest(input, allowEmbedded) {
|
|
36
|
+
const normalized = normalizeText(input);
|
|
37
|
+
if (normalized.length === 0 || isNegatedRememberRequest(normalized)) return false;
|
|
38
|
+
|
|
39
|
+
const anchored = [
|
|
40
|
+
/^(?:please\s+)?remember\s+(?:this|that|my|the\s+following|that\b)/,
|
|
41
|
+
/^(?:please\s+)?(?:save|keep)\s+(?:this|that)\s+(?:in\s+(?:your\s+)?memory|for\s+later)\b/,
|
|
42
|
+
/^(?:bitte\s+)?merk(?:e)?\s+dir(?:\s*[:,]|\s+(?:das|dies|dass|mein(?:e|en)?|folgendes)\b)/,
|
|
43
|
+
/^(?:kannst\s+du\s+dir\s+(?:bitte\s+)?|bitte\s+)merken\s*[,]?\s+dass\b/,
|
|
44
|
+
/^(?:bitte\s+)?speicher(?:e)?\s+(?:dir\s+)?(?:das|dies|folgendes)\b/,
|
|
45
|
+
/^(?:por\s+favor\s+)?recuerda(?:\s*[:,]|\s+(?:esto|eso|que|mi)\b)/,
|
|
46
|
+
/^(?:por\s+favor\s+)?guarda\s+(?:esto|eso)\s+en\s+(?:la\s+)?memoria\b/,
|
|
47
|
+
/^(?:s'il\s+te\s+plait\s*[,]?\s+)?memorise(?:\s*[:,]|\s+(?:ceci|cela|que|mon|ma|mes)\b)/,
|
|
48
|
+
/^(?:s'il\s+te\s+plait\s*[,]?\s+)?souviens-toi(?:\s*[:,]|\s+(?:de|que|ceci|cela)\b)/,
|
|
49
|
+
/^(?:snalla\s+)?kom\s+ihag(?:\s*[:,]|\s+(?:detta|det|att|min|mitt|mina)\b)/,
|
|
50
|
+
/^(?:snalla\s+)?spara\s+(?:det\s+har|detta)\s+i\s+minnet\b/,
|
|
51
|
+
/^(?:prosim\s+)?zapamatuj\s+si(?:\s*[:,]|\s+(?:to|ze|moje|muj|mou)\b)/,
|
|
52
|
+
/^(?:prosim\s+)?uloz\s+si\s+(?:to|toto)\s+do\s+pameti\b/,
|
|
53
|
+
];
|
|
54
|
+
if (anchored.some((pattern) => pattern.test(normalized))) return true;
|
|
55
|
+
if (!allowEmbedded) return false;
|
|
56
|
+
|
|
57
|
+
return [
|
|
58
|
+
/\b(?:please\s+)?remember\s+(?:this|that|my|the\s+following)\b/,
|
|
59
|
+
/\b(?:bitte\s+)?merk(?:e)?\s+dir\s+(?:das|dies|dass|folgendes)\b/,
|
|
60
|
+
/\bspeicher(?:e)?\s+(?:dir\s+)?(?:das|dies|folgendes)\b/,
|
|
61
|
+
].some((pattern) => pattern.test(normalized));
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function shouldArmPersonalMemoryRememberIntent(input, options = {}) {
|
|
65
|
+
if (options.permissionMode === 'yolo') return true;
|
|
66
|
+
return isExplicitRememberRequest(input, options.channel === true);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
module.exports = {
|
|
70
|
+
personalMemoryMutationDecision,
|
|
71
|
+
shouldArmPersonalMemoryRememberIntent,
|
|
72
|
+
};
|
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const crypto = require('node:crypto');
|
|
4
|
+
const fs = require('node:fs');
|
|
5
|
+
const os = require('node:os');
|
|
6
|
+
const path = require('node:path');
|
|
7
|
+
|
|
8
|
+
const RELAY_VERSION = 1;
|
|
9
|
+
const DEFAULT_TIMEOUT_MS = 60_000;
|
|
10
|
+
const DEFAULT_POLL_INTERVAL_MS = 250;
|
|
11
|
+
const MAX_LABEL_CHARS = 48;
|
|
12
|
+
const ALLOWED_RESPONSES = new Set(['approved', 'approved_for_session', 'rejected']);
|
|
13
|
+
|
|
14
|
+
function relayRoot(homeDir = os.homedir()) {
|
|
15
|
+
return path.join(homeDir, '.blun', 'channels', 'telegram', 'approvals');
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function relayPaths(homeDir) {
|
|
19
|
+
const root = relayRoot(homeDir);
|
|
20
|
+
return {
|
|
21
|
+
root,
|
|
22
|
+
pending: path.join(root, 'pending'),
|
|
23
|
+
responses: path.join(root, 'responses'),
|
|
24
|
+
resolved: path.join(root, 'resolved'),
|
|
25
|
+
sent: path.join(root, 'sent'),
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function ensureRelayDirs(homeDir) {
|
|
30
|
+
const dirs = relayPaths(homeDir);
|
|
31
|
+
for (const dir of Object.values(dirs).slice(1)) {
|
|
32
|
+
fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
33
|
+
}
|
|
34
|
+
return dirs;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function safeId(value) {
|
|
38
|
+
return crypto.createHash('sha256').update(String(value)).digest('hex').slice(0, 24);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function requestFileName(requestId) {
|
|
42
|
+
return `${safeId(requestId)}.json`;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function atomicWriteJson(file, value) {
|
|
46
|
+
const temp = `${file}.${process.pid}.${crypto.randomBytes(4).toString('hex')}.tmp`;
|
|
47
|
+
fs.writeFileSync(temp, `${JSON.stringify(value)}\n`, { encoding: 'utf8', mode: 0o600 });
|
|
48
|
+
fs.renameSync(temp, file);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function readJson(file) {
|
|
52
|
+
try {
|
|
53
|
+
return JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
54
|
+
} catch {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function compactLabel(value) {
|
|
60
|
+
const normalized = String(value ?? '').replace(/\s+/gu, ' ').trim();
|
|
61
|
+
if (normalized.length <= MAX_LABEL_CHARS) return normalized;
|
|
62
|
+
return `${normalized.slice(0, MAX_LABEL_CHARS - 1)}…`;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function resolveTelegramApprovalTarget(allowFrom, configuredChatId) {
|
|
66
|
+
const allowed = Array.isArray(allowFrom)
|
|
67
|
+
? [...new Set(allowFrom.map(String).filter((value) => value.length > 0))]
|
|
68
|
+
: [];
|
|
69
|
+
const configured = String(configuredChatId ?? '').trim();
|
|
70
|
+
if (configured.length > 0) return allowed.includes(configured) ? configured : null;
|
|
71
|
+
return allowed.length === 1 ? allowed[0] : null;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function parseTelegramApprovalCallback(data) {
|
|
75
|
+
const match = /^blun-appr:([a-f0-9]{24}):(\d+)$/u.exec(String(data ?? ''));
|
|
76
|
+
if (match === null) return null;
|
|
77
|
+
const choiceIndex = Number(match[2]);
|
|
78
|
+
if (!Number.isSafeInteger(choiceIndex)) return null;
|
|
79
|
+
return { requestId: match[1], choiceIndex };
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function buildTelegramApprovalCard(request) {
|
|
83
|
+
return {
|
|
84
|
+
text: request.toolName,
|
|
85
|
+
reply_markup: {
|
|
86
|
+
inline_keyboard: request.choices.map((choice) => [{
|
|
87
|
+
text: choice.label,
|
|
88
|
+
callback_data: `blun-appr:${request.requestId}:${choice.index}`,
|
|
89
|
+
}]),
|
|
90
|
+
},
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function isAuthorizedTelegramApprovalCallback(expectedChatId, senderId, chatId) {
|
|
95
|
+
if (expectedChatId === null || expectedChatId === undefined) return false;
|
|
96
|
+
return String(senderId ?? '') === String(expectedChatId)
|
|
97
|
+
&& String(chatId ?? '') === String(expectedChatId);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function shouldRelayTelegramApproval(permissionMode) {
|
|
101
|
+
return permissionMode !== 'yolo';
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function buildTelegramApprovalRequest(payload, options = {}) {
|
|
105
|
+
const now = Number.isFinite(options.now) ? options.now : Date.now();
|
|
106
|
+
const timeoutMs = Number.isFinite(options.timeoutMs) && options.timeoutMs > 0
|
|
107
|
+
? options.timeoutMs
|
|
108
|
+
: DEFAULT_TIMEOUT_MS;
|
|
109
|
+
const requestId = crypto.randomBytes(12).toString('hex');
|
|
110
|
+
const nonce = crypto.randomBytes(16).toString('hex');
|
|
111
|
+
const choices = (Array.isArray(payload?.choices) ? payload.choices : [])
|
|
112
|
+
.filter((choice) => choice?.requires_feedback !== true && ALLOWED_RESPONSES.has(choice?.response))
|
|
113
|
+
.map((choice, index) => ({
|
|
114
|
+
index,
|
|
115
|
+
label: compactLabel(choice.label || choice.response),
|
|
116
|
+
response: choice.response,
|
|
117
|
+
}));
|
|
118
|
+
if (choices.length === 0) return null;
|
|
119
|
+
return {
|
|
120
|
+
v: RELAY_VERSION,
|
|
121
|
+
requestId,
|
|
122
|
+
nonce,
|
|
123
|
+
approvalId: String(payload?.id ?? ''),
|
|
124
|
+
toolName: compactLabel(payload?.tool_name || 'Werkzeug'),
|
|
125
|
+
choices,
|
|
126
|
+
createdAt: now,
|
|
127
|
+
expiresAt: now + timeoutMs,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function responseForChoice(request, choiceIndex) {
|
|
132
|
+
const choice = request?.choices?.find((candidate) => candidate.index === choiceIndex);
|
|
133
|
+
if (choice === undefined) return null;
|
|
134
|
+
if (choice.response === 'approved_for_session') {
|
|
135
|
+
return { decision: 'approved', scope: 'session', selectedLabel: choice.label };
|
|
136
|
+
}
|
|
137
|
+
return {
|
|
138
|
+
decision: choice.response === 'approved' ? 'approved' : 'rejected',
|
|
139
|
+
selectedLabel: choice.label,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function validateTelegramApprovalResponse(request, response, now = Date.now()) {
|
|
144
|
+
if (request?.v !== RELAY_VERSION || response?.v !== RELAY_VERSION) return null;
|
|
145
|
+
if (request.requestId !== response.requestId || request.nonce !== response.nonce) return null;
|
|
146
|
+
if (!Number.isFinite(request.expiresAt) || now > request.expiresAt) return null;
|
|
147
|
+
if (!Number.isInteger(response.choiceIndex)) return null;
|
|
148
|
+
return responseForChoice(request, response.choiceIndex);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function beginTelegramApprovalRelay(payload, options = {}) {
|
|
152
|
+
const request = buildTelegramApprovalRequest(payload, options);
|
|
153
|
+
if (request === null) return null;
|
|
154
|
+
const dirs = ensureRelayDirs(options.homeDir);
|
|
155
|
+
const fileName = requestFileName(request.requestId);
|
|
156
|
+
const pendingFile = path.join(dirs.pending, fileName);
|
|
157
|
+
const responseFile = path.join(dirs.responses, fileName);
|
|
158
|
+
const resolvedFile = path.join(dirs.resolved, fileName);
|
|
159
|
+
atomicWriteJson(pendingFile, request);
|
|
160
|
+
|
|
161
|
+
let settled = false;
|
|
162
|
+
let interval;
|
|
163
|
+
let resolvePromise;
|
|
164
|
+
const promise = new Promise((resolve) => { resolvePromise = resolve; });
|
|
165
|
+
const finish = (response) => {
|
|
166
|
+
if (settled) return false;
|
|
167
|
+
settled = true;
|
|
168
|
+
if (interval !== undefined) clearInterval(interval);
|
|
169
|
+
atomicWriteJson(resolvedFile, {
|
|
170
|
+
v: RELAY_VERSION,
|
|
171
|
+
requestId: request.requestId,
|
|
172
|
+
resolvedAt: Date.now(),
|
|
173
|
+
response,
|
|
174
|
+
});
|
|
175
|
+
resolvePromise(response);
|
|
176
|
+
return true;
|
|
177
|
+
};
|
|
178
|
+
const poll = () => {
|
|
179
|
+
if (settled) return;
|
|
180
|
+
if (Date.now() > request.expiresAt) {
|
|
181
|
+
finish(null);
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
const raw = readJson(responseFile);
|
|
185
|
+
if (raw === null) return;
|
|
186
|
+
const response = validateTelegramApprovalResponse(request, raw);
|
|
187
|
+
if (response !== null) finish(response);
|
|
188
|
+
};
|
|
189
|
+
interval = setInterval(poll, options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS);
|
|
190
|
+
interval.unref?.();
|
|
191
|
+
return {
|
|
192
|
+
request,
|
|
193
|
+
promise,
|
|
194
|
+
complete: finish,
|
|
195
|
+
poll,
|
|
196
|
+
dispose: () => {
|
|
197
|
+
if (interval !== undefined) clearInterval(interval);
|
|
198
|
+
interval = undefined;
|
|
199
|
+
},
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function listPendingTelegramApprovals(options = {}) {
|
|
204
|
+
const dirs = ensureRelayDirs(options.homeDir);
|
|
205
|
+
const now = Number.isFinite(options.now) ? options.now : Date.now();
|
|
206
|
+
const results = [];
|
|
207
|
+
for (const name of fs.readdirSync(dirs.pending)) {
|
|
208
|
+
if (!name.endsWith('.json')) continue;
|
|
209
|
+
if (fs.existsSync(path.join(dirs.resolved, name))) continue;
|
|
210
|
+
const request = readJson(path.join(dirs.pending, name));
|
|
211
|
+
if (request?.v !== RELAY_VERSION || now > request.expiresAt) continue;
|
|
212
|
+
results.push(request);
|
|
213
|
+
}
|
|
214
|
+
return results.sort((left, right) => left.createdAt - right.createdAt);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function writeTelegramApprovalResponse(request, choiceIndex, metadata = {}, options = {}) {
|
|
218
|
+
if (responseForChoice(request, choiceIndex) === null) return false;
|
|
219
|
+
const dirs = ensureRelayDirs(options.homeDir);
|
|
220
|
+
const fileName = requestFileName(request.requestId);
|
|
221
|
+
if (fs.existsSync(path.join(dirs.resolved, fileName))) return false;
|
|
222
|
+
const response = {
|
|
223
|
+
v: RELAY_VERSION,
|
|
224
|
+
requestId: request.requestId,
|
|
225
|
+
nonce: request.nonce,
|
|
226
|
+
choiceIndex,
|
|
227
|
+
senderId: String(metadata.senderId ?? ''),
|
|
228
|
+
chatId: String(metadata.chatId ?? ''),
|
|
229
|
+
respondedAt: Number.isFinite(metadata.now) ? metadata.now : Date.now(),
|
|
230
|
+
};
|
|
231
|
+
const file = path.join(dirs.responses, fileName);
|
|
232
|
+
try {
|
|
233
|
+
fs.writeFileSync(file, `${JSON.stringify(response)}\n`, { encoding: 'utf8', mode: 0o600, flag: 'wx' });
|
|
234
|
+
return true;
|
|
235
|
+
} catch (error) {
|
|
236
|
+
if (error?.code === 'EEXIST') return false;
|
|
237
|
+
throw error;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function markTelegramApprovalSent(request, metadata = {}, options = {}) {
|
|
242
|
+
const dirs = ensureRelayDirs(options.homeDir);
|
|
243
|
+
const file = path.join(dirs.sent, requestFileName(request.requestId));
|
|
244
|
+
const value = {
|
|
245
|
+
v: RELAY_VERSION,
|
|
246
|
+
requestId: request.requestId,
|
|
247
|
+
chatId: String(metadata.chatId ?? ''),
|
|
248
|
+
messageId: String(metadata.messageId ?? ''),
|
|
249
|
+
sentAt: Number.isFinite(metadata.now) ? metadata.now : Date.now(),
|
|
250
|
+
};
|
|
251
|
+
try {
|
|
252
|
+
fs.writeFileSync(file, `${JSON.stringify(value)}\n`, { encoding: 'utf8', mode: 0o600, flag: 'wx' });
|
|
253
|
+
return true;
|
|
254
|
+
} catch (error) {
|
|
255
|
+
if (error?.code === 'EEXIST') return false;
|
|
256
|
+
throw error;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function wasTelegramApprovalSent(request, options = {}) {
|
|
261
|
+
const dirs = ensureRelayDirs(options.homeDir);
|
|
262
|
+
return fs.existsSync(path.join(dirs.sent, requestFileName(request.requestId)));
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
module.exports = {
|
|
266
|
+
DEFAULT_TIMEOUT_MS,
|
|
267
|
+
beginTelegramApprovalRelay,
|
|
268
|
+
buildTelegramApprovalCard,
|
|
269
|
+
buildTelegramApprovalRequest,
|
|
270
|
+
listPendingTelegramApprovals,
|
|
271
|
+
markTelegramApprovalSent,
|
|
272
|
+
relayPaths,
|
|
273
|
+
resolveTelegramApprovalTarget,
|
|
274
|
+
parseTelegramApprovalCallback,
|
|
275
|
+
isAuthorizedTelegramApprovalCallback,
|
|
276
|
+
requestFileName,
|
|
277
|
+
responseForChoice,
|
|
278
|
+
shouldRelayTelegramApproval,
|
|
279
|
+
validateTelegramApprovalResponse,
|
|
280
|
+
wasTelegramApprovalSent,
|
|
281
|
+
writeTelegramApprovalResponse,
|
|
282
|
+
};
|
package/bin/update-notice.js
CHANGED
|
@@ -610,6 +610,7 @@ function writeCoreInstallSuccess(
|
|
|
610
610
|
fromVersion,
|
|
611
611
|
version,
|
|
612
612
|
releaseNotes,
|
|
613
|
+
initiatedBy,
|
|
613
614
|
now = Date.now(),
|
|
614
615
|
) {
|
|
615
616
|
if (!parseSemver(fromVersion) || !parseSemver(version)) {
|
|
@@ -631,6 +632,7 @@ function writeCoreInstallSuccess(
|
|
|
631
632
|
version,
|
|
632
633
|
fromVersion,
|
|
633
634
|
...(releaseNotes === undefined ? {} : { releaseNotes }),
|
|
635
|
+
initiatedBy,
|
|
634
636
|
installedAt: new Date(now).toISOString(),
|
|
635
637
|
notifiedAt: null,
|
|
636
638
|
},
|
|
@@ -1597,6 +1599,11 @@ async function runUpdateFlow(options, explicitUpdate) {
|
|
|
1597
1599
|
currentVersion,
|
|
1598
1600
|
release.version,
|
|
1599
1601
|
release.releaseNotes,
|
|
1602
|
+
explicitUpdate || queuedForRelease
|
|
1603
|
+
? 'manual'
|
|
1604
|
+
: automaticPreparedRuntimeReady
|
|
1605
|
+
? 'automatic'
|
|
1606
|
+
: 'startup_prompt',
|
|
1600
1607
|
(options.now || Date.now)(),
|
|
1601
1608
|
);
|
|
1602
1609
|
} catch {
|
package/blun.mjs
CHANGED
|
@@ -233640,14 +233640,20 @@ var init_plan_mode_tool_approve = __esmMin((() => {
|
|
|
233640
233640
|
function isPersonalMemoryMutationTool(toolName) {
|
|
233641
233641
|
return PERSONAL_MEMORY_MUTATION_TOOLS.has(toolName.toLowerCase());
|
|
233642
233642
|
}
|
|
233643
|
+
var personalMemoryConsentPolicy = createRequire(import.meta.url)("./bin/personal-memory-consent-policy.cjs");
|
|
233643
233644
|
var PERSONAL_MEMORY_MUTATION_TOOLS, PersonalMemoryMutationAlwaysAskPermissionPolicy;
|
|
233644
233645
|
var init_personal_memory_mutation_always_ask = __esmMin((() => {
|
|
233645
233646
|
PERSONAL_MEMORY_MUTATION_TOOLS = new Set(["mcp__personal-memory__memory_settings_update", "mcp__personal-memory__memory_remember"]);
|
|
233646
233647
|
PersonalMemoryMutationAlwaysAskPermissionPolicy = class {
|
|
233648
|
+
agent;
|
|
233647
233649
|
name = "personal-memory-mutation-always-ask";
|
|
233650
|
+
constructor(agent) {
|
|
233651
|
+
this.agent = agent;
|
|
233652
|
+
}
|
|
233648
233653
|
evaluate(context) {
|
|
233649
|
-
|
|
233650
|
-
|
|
233654
|
+
const decision = personalMemoryConsentPolicy.personalMemoryMutationDecision(this.agent.permission.mode, context.toolCall.name);
|
|
233655
|
+
if (decision === void 0) return;
|
|
233656
|
+
return { kind: decision };
|
|
233651
233657
|
}
|
|
233652
233658
|
};
|
|
233653
233659
|
}));
|
|
@@ -233860,7 +233866,7 @@ function createPermissionDecisionPolicies(agent) {
|
|
|
233860
233866
|
new AutoModeAskUserQuestionDenyPermissionPolicy(agent),
|
|
233861
233867
|
new PlanModeGuardDenyPermissionPolicy(agent),
|
|
233862
233868
|
new UserConfiguredDenyPermissionPolicy(agent),
|
|
233863
|
-
new PersonalMemoryMutationAlwaysAskPermissionPolicy(),
|
|
233869
|
+
new PersonalMemoryMutationAlwaysAskPermissionPolicy(agent),
|
|
233864
233870
|
new DesktopControlAlwaysAskPermissionPolicy(agent),
|
|
233865
233871
|
new AutoModeApprovePermissionPolicy(agent),
|
|
233866
233872
|
new SessionApprovalHistoryPermissionPolicy(agent),
|
|
@@ -501805,6 +501811,7 @@ registerUiCatalogFragment({
|
|
|
501805
501811
|
});
|
|
501806
501812
|
//#endregion
|
|
501807
501813
|
//#region src/tui/reverse-rpc/approval/handler.ts
|
|
501814
|
+
var { beginTelegramApprovalRelay, shouldRelayTelegramApproval } = createRequire(import.meta.url)("./bin/telegram-approval-relay.cjs");
|
|
501808
501815
|
function approvalCancellationFeedback(context) {
|
|
501809
501816
|
switch (context) {
|
|
501810
501817
|
case "switching_session": return uiText("approvalFeedback.switchingSession");
|
|
@@ -501813,10 +501820,20 @@ function approvalCancellationFeedback(context) {
|
|
|
501813
501820
|
case "shutting_down": return uiText("approvalFeedback.shuttingDown");
|
|
501814
501821
|
}
|
|
501815
501822
|
}
|
|
501816
|
-
function createApprovalRequestHandler(controller, onResponse) {
|
|
501823
|
+
function createApprovalRequestHandler(controller, onResponse, options = {}) {
|
|
501817
501824
|
return async (event) => {
|
|
501825
|
+
let relay;
|
|
501818
501826
|
try {
|
|
501819
|
-
const
|
|
501827
|
+
const payload = adaptApprovalRequest(event);
|
|
501828
|
+
const pending = controller.show(payload);
|
|
501829
|
+
if (options.telegramEnabled?.() === true) {
|
|
501830
|
+
relay = beginTelegramApprovalRelay(payload);
|
|
501831
|
+
relay?.promise.then((response) => {
|
|
501832
|
+
if (response !== null) controller.respondById(payload.id, response);
|
|
501833
|
+
}).catch(() => {});
|
|
501834
|
+
}
|
|
501835
|
+
const response = await pending;
|
|
501836
|
+
relay?.complete(response);
|
|
501820
501837
|
onResponse?.(event, response);
|
|
501821
501838
|
return response;
|
|
501822
501839
|
} catch {
|
|
@@ -501826,6 +501843,8 @@ function createApprovalRequestHandler(controller, onResponse) {
|
|
|
501826
501843
|
};
|
|
501827
501844
|
onResponse?.(event, response);
|
|
501828
501845
|
return response;
|
|
501846
|
+
} finally {
|
|
501847
|
+
relay?.dispose();
|
|
501829
501848
|
}
|
|
501830
501849
|
};
|
|
501831
501850
|
}
|
|
@@ -505125,9 +505144,9 @@ function isRecord$2(value) {
|
|
|
505125
505144
|
//#region src/personal-memory/remember-intent.ts
|
|
505126
505145
|
const activeIntents = /* @__PURE__ */ new Map();
|
|
505127
505146
|
/** Arm an attestation only for an explicit request submitted by the trusted host. */
|
|
505128
|
-
function armPersonalMemoryRememberIntent(sessionId, input) {
|
|
505147
|
+
function armPersonalMemoryRememberIntent(sessionId, input, options = {}) {
|
|
505129
505148
|
activeIntents.delete(sessionId);
|
|
505130
|
-
if (!
|
|
505149
|
+
if (!personalMemoryConsentPolicy.shouldArmPersonalMemoryRememberIntent(input, options)) return;
|
|
505131
505150
|
activeIntents.set(sessionId, { consumed: false });
|
|
505132
505151
|
}
|
|
505133
505152
|
/** Bind the pending attestation to the exact turn allocated for that prompt. */
|
|
@@ -513226,6 +513245,17 @@ var ReverseRpcController = class {
|
|
|
513226
513245
|
if (pending !== null) this.drainAutoResolved(pending.payload, data);
|
|
513227
513246
|
this.advanceOrHide();
|
|
513228
513247
|
}
|
|
513248
|
+
respondById(id, data) {
|
|
513249
|
+
if (this.current?.payload?.id === id) {
|
|
513250
|
+
this.respond(data);
|
|
513251
|
+
return true;
|
|
513252
|
+
}
|
|
513253
|
+
const index = this.queue.findIndex((entry) => entry.payload?.id === id);
|
|
513254
|
+
if (index < 0) return false;
|
|
513255
|
+
const [pending] = this.queue.splice(index, 1);
|
|
513256
|
+
pending.resolve(data);
|
|
513257
|
+
return true;
|
|
513258
|
+
}
|
|
513229
513259
|
/** Cancels all pending requests during shutdown or session switches. */
|
|
513230
513260
|
cancelAll(reason) {
|
|
513231
513261
|
const all = [...this.current === null ? [] : [this.current], ...this.queue];
|
|
@@ -517238,6 +517268,10 @@ var BlunTUI = class {
|
|
|
517238
517268
|
});
|
|
517239
517269
|
}
|
|
517240
517270
|
sendChannelMessageInternal(session, modelInput, displayText, origin, channelChatId, channelImagePath, contextOnly = false, transcriptRendered = false, acknowledge, channelAttention = false, queueKey, channelDirect = false, channelDirectResume = false) {
|
|
517271
|
+
armPersonalMemoryRememberIntent(session.id, displayText, {
|
|
517272
|
+
permissionMode: this.state.appState.permissionMode,
|
|
517273
|
+
channel: true
|
|
517274
|
+
});
|
|
517241
517275
|
if (!transcriptRendered) this.appendTranscriptEntry({
|
|
517242
517276
|
id: nextTranscriptId(),
|
|
517243
517277
|
kind: "user",
|
|
@@ -517608,7 +517642,10 @@ var BlunTUI = class {
|
|
|
517608
517642
|
this.sessionEventHandler.requestQueuedGoalPromotion();
|
|
517609
517643
|
}
|
|
517610
517644
|
sendMessageInternal(session, input, options) {
|
|
517611
|
-
armPersonalMemoryRememberIntent(session.id, input
|
|
517645
|
+
armPersonalMemoryRememberIntent(session.id, input, {
|
|
517646
|
+
permissionMode: this.state.appState.permissionMode,
|
|
517647
|
+
channel: false
|
|
517648
|
+
});
|
|
517612
517649
|
const imageAttachmentIds = options?.imageAttachmentIds !== void 0 && options.imageAttachmentIds.length > 0 ? options.imageAttachmentIds : void 0;
|
|
517613
517650
|
this.appendTranscriptEntry({
|
|
517614
517651
|
id: nextTranscriptId(),
|
|
@@ -517934,7 +517971,7 @@ var BlunTUI = class {
|
|
|
517934
517971
|
registerSessionHandlers(session) {
|
|
517935
517972
|
session.setApprovalHandler(createApprovalRequestHandler(this.approvalController, (request, response) => {
|
|
517936
517973
|
this.appendApprovalTranscriptEntry(request, response);
|
|
517937
|
-
}));
|
|
517974
|
+
}, { telegramEnabled: () => shouldRelayTelegramApproval(this.state.appState.permissionMode) }));
|
|
517938
517975
|
session.setQuestionHandler(createQuestionAskHandler(this.questionController));
|
|
517939
517976
|
}
|
|
517940
517977
|
async fetchSessions(scope = this.state.sessionsScope) {
|
|
@@ -519592,6 +519629,7 @@ const UpdateInstallStateSchema = object({
|
|
|
519592
519629
|
version: string().min(1),
|
|
519593
519630
|
fromVersion: string().min(1).optional(),
|
|
519594
519631
|
releaseNotes: ReleaseNotesSchema.optional(),
|
|
519632
|
+
initiatedBy: _enum(["automatic", "startup_prompt", "manual", "unknown"]).optional(),
|
|
519595
519633
|
installedAt: string().min(1),
|
|
519596
519634
|
notifiedAt: string().min(1).nullable()
|
|
519597
519635
|
}).strict().nullable()
|
|
@@ -520319,6 +520357,7 @@ async function runUpdatePreflight(currentVersion, options = {}) {
|
|
|
520319
520357
|
version: userVisibleTarget.version,
|
|
520320
520358
|
fromVersion: currentVersion,
|
|
520321
520359
|
releaseNotes: userVisibleTarget.releaseNotes,
|
|
520360
|
+
initiatedBy: "startup_prompt",
|
|
520322
520361
|
installedAt: nowIso(),
|
|
520323
520362
|
notifiedAt: null
|
|
520324
520363
|
}
|
package/package.json
CHANGED
|
@@ -7,7 +7,9 @@ import { Readable, Writable } from "node:stream";
|
|
|
7
7
|
import { fileURLToPath } from "node:url";
|
|
8
8
|
import { hostname } from "node:os";
|
|
9
9
|
import remoteStatusPolicy from "../../bin/telegram-remote-status-policy.cjs";
|
|
10
|
+
import telegramApprovalRelay from "../../bin/telegram-approval-relay.cjs";
|
|
10
11
|
const { buildTelegramRemoteStatus, resolveTelegramRemoteVersion } = remoteStatusPolicy;
|
|
12
|
+
const { buildTelegramApprovalCard, isAuthorizedTelegramApprovalCallback, listPendingTelegramApprovals, markTelegramApprovalSent, parseTelegramApprovalCallback, resolveTelegramApprovalTarget, wasTelegramApprovalSent, writeTelegramApprovalResponse } = telegramApprovalRelay;
|
|
11
13
|
//#region ../../node_modules/pathe/dist/shared/pathe.M-eThtNZ.mjs
|
|
12
14
|
const _DRIVE_LETTER_START_RE = /^[A-Za-z]:\//;
|
|
13
15
|
function normalizeWindowsPath(input = "") {
|
|
@@ -4432,6 +4434,36 @@ function startApprovalWatcher(bot) {
|
|
|
4432
4434
|
}
|
|
4433
4435
|
}, 5e3).unref();
|
|
4434
4436
|
}
|
|
4437
|
+
|
|
4438
|
+
function telegramApprovalTarget() {
|
|
4439
|
+
const access = readAccess();
|
|
4440
|
+
return resolveTelegramApprovalTarget(access.allowFrom, process.env.BLUN_TELEGRAM_APPROVAL_CHAT_ID);
|
|
4441
|
+
}
|
|
4442
|
+
|
|
4443
|
+
let approvalSweepBusy = false;
|
|
4444
|
+
function startToolApprovalWatcher(bot) {
|
|
4445
|
+
setInterval(async () => {
|
|
4446
|
+
if (approvalSweepBusy) return;
|
|
4447
|
+
approvalSweepBusy = true;
|
|
4448
|
+
try {
|
|
4449
|
+
const chatId = telegramApprovalTarget();
|
|
4450
|
+
if (chatId === null) return;
|
|
4451
|
+
for (const request of listPendingTelegramApprovals()) {
|
|
4452
|
+
if (wasTelegramApprovalSent(request)) continue;
|
|
4453
|
+
const card = buildTelegramApprovalCard(request);
|
|
4454
|
+
const message = await bot.api.sendMessage(chatId, card.text, { reply_markup: card.reply_markup });
|
|
4455
|
+
markTelegramApprovalSent(request, {
|
|
4456
|
+
chatId,
|
|
4457
|
+
messageId: message.message_id
|
|
4458
|
+
});
|
|
4459
|
+
}
|
|
4460
|
+
} catch (error) {
|
|
4461
|
+
process.stderr.write(`telegram channel: approval relay failed: ${String(error)}\n`);
|
|
4462
|
+
} finally {
|
|
4463
|
+
approvalSweepBusy = false;
|
|
4464
|
+
}
|
|
4465
|
+
}, 500).unref();
|
|
4466
|
+
}
|
|
4435
4467
|
/**
|
|
4436
4468
|
* Retry polling with backoff on ANY error — a single ETIMEDOUT/ECONNRESET
|
|
4437
4469
|
* must not leave the bridge deaf while the process stays alive. Persistent
|
|
@@ -4662,6 +4694,26 @@ async function handleInbound(event) {
|
|
|
4662
4694
|
else process.stderr.write(`telegram bridge: headless off — inbound ${chatId}:${String(msgId)} nicht zugestellt (kein TUI-Fenster offen)\n`);
|
|
4663
4695
|
}
|
|
4664
4696
|
registerTypeHandlers(bot, TOKEN, handleInbound);
|
|
4697
|
+
bot.on("callback_query:data", async (ctx) => {
|
|
4698
|
+
const callback = parseTelegramApprovalCallback(ctx.callbackQuery.data);
|
|
4699
|
+
if (callback === null) return;
|
|
4700
|
+
const request = listPendingTelegramApprovals().find((candidate) => candidate.requestId === callback.requestId);
|
|
4701
|
+
const expectedChatId = telegramApprovalTarget();
|
|
4702
|
+
if (request === void 0) {
|
|
4703
|
+
await ctx.answerCallbackQuery();
|
|
4704
|
+
return;
|
|
4705
|
+
}
|
|
4706
|
+
if (!isAuthorizedTelegramApprovalCallback(expectedChatId, ctx.from.id, ctx.chat?.id)) {
|
|
4707
|
+
await ctx.answerCallbackQuery();
|
|
4708
|
+
return;
|
|
4709
|
+
}
|
|
4710
|
+
const accepted = writeTelegramApprovalResponse(request, callback.choiceIndex, {
|
|
4711
|
+
senderId: ctx.from.id,
|
|
4712
|
+
chatId: ctx.chat.id
|
|
4713
|
+
});
|
|
4714
|
+
await ctx.answerCallbackQuery();
|
|
4715
|
+
if (accepted && ctx.callbackQuery.message !== void 0) await ctx.api.editMessageReplyMarkup(ctx.chat.id, ctx.callbackQuery.message.message_id, { inline_keyboard: [] }).catch(() => {});
|
|
4716
|
+
});
|
|
4665
4717
|
bot.command("start", async (ctx) => {
|
|
4666
4718
|
if (dmCommandGate(toInboundCtx(ctx, "")) === null) return;
|
|
4667
4719
|
await ctx.reply("This bot bridges Telegram to a BLUN session.\n\nTo pair:\n1. DM me anything — you'll get a 6-char code\n2. In your BLUN terminal: /telegram:access pair <code>\n\nAfter that, DMs here reach that session.");
|
|
@@ -4748,6 +4800,7 @@ if (!HEADLESS_ENABLED) {
|
|
|
4748
4800
|
}, 2e4).unref();
|
|
4749
4801
|
}
|
|
4750
4802
|
startApprovalWatcher(bot);
|
|
4803
|
+
startToolApprovalWatcher(bot);
|
|
4751
4804
|
pollWithBackoff(bot, () => shuttingDown, (username) => {
|
|
4752
4805
|
botUsername = username;
|
|
4753
4806
|
});
|