blun-king-cli 9.1.61 → 9.1.63
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/LIESMICH.txt +11 -2
- package/README.md +9 -0
- package/bin/context-performance-policy.cjs +18 -0
- package/bin/launcher-runtime.js +2 -2
- package/bin/package-regression-policy.cjs +77 -0
- package/bin/provider-idle-timeout-policy.cjs +14 -0
- package/bin/skill-listing-performance-policy.cjs +33 -0
- package/bin/standard-tools-bootstrap.js +80 -26
- package/bin/turn-thinking-policy.cjs +66 -0
- package/bin/turn-tool-performance-policy.cjs +141 -0
- package/blun.mjs +523 -224
- package/package.json +4 -1
- package/release-planned-removals.json +4 -0
- package/scripts/check-package-regression.js +38 -0
- package/standard-skills/agent-browser/SKILL.md +19 -0
- package/standard-skills/agent-browser/references/runtime.md +8 -0
- package/standard-skills/design-taste-frontend/SKILL.md +1206 -0
- package/standard-skills/full-output-enforcement/SKILL.md +49 -0
- package/standard-skills/high-end-visual-design/SKILL.md +98 -0
- package/standard-skills/image-to-code/SKILL.md +1228 -0
- package/standard-skills/industrial-brutalist-ui/SKILL.md +92 -0
- package/standard-skills/minimalist-ui/SKILL.md +85 -0
- package/standard-skills/motion-design-taste/SKILL.md +74 -0
- package/standard-skills/playwright-testing/SKILL.md +19 -0
- package/standard-skills/playwright-testing/references/runtime.md +7 -0
- package/standard-skills/premortem/SKILL.md +148 -0
- package/standard-skills/redesign-existing-projects/SKILL.md +178 -0
- package/standard-skills/screenshot-lesen/SKILL.md +52 -0
- package/standard-skills/stitch-design-taste/DESIGN.md +121 -0
- package/standard-skills/stitch-design-taste/SKILL.md +184 -0
- package/standard-skills/telegram-channel/SKILL.md +18 -0
- package/standard-skills/telegram-channel/references/runtime.md +7 -0
- package/standard-skills/translate-native/LICENSE +21 -0
- package/standard-skills/translate-native/SKILL.md +39 -0
- package/standard-skills/translate-native/VERSION +1 -0
- package/standard-skills/translate-native/provenance.json +7 -0
- package/standard-skills/translate-native/references/evaluation-protocol.md +95 -0
- package/standard-skills/translate-native/references/native-orthography.md +79 -0
- package/standard-skills/translate-native/references/native-translation-standard.md +94 -0
- package/standard-skills/translate-native/references/structured-content.md +72 -0
- package/standard-skills/translate-native/references/translationese-review.md +77 -0
- package/standard-skills/translate-native/scripts/blun_language_guard.py +686 -0
- package/standard-skills/translate-native/scripts/check_diacritics.py +353 -0
- package/standard-skills/translate-native/scripts/guard_service_client.py +82 -0
- package/standard-skills/translate-native/scripts/language_gateway.py +62 -0
- package/standard-skills/translate-native/scripts/language_quality.py +172 -0
- package/standard-skills/translate-native/scripts/pre_output_guard.py +67 -0
- package/standard-skills/translate-native/scripts/translation_guard.py +916 -0
- package/standard-skills/web-lesen/SKILL.md +58 -0
- package/standard-skills/windows-mcp/SKILL.md +19 -0
- package/standard-skills/windows-mcp/references/runtime.md +9 -0
- package/telegram-plugin/commands/access.md +10 -2
- package/telegram-plugin/dist/bridge.mjs +505 -63776
- package/telegram-plugin/dist/mcp-server.mjs +8976 -72843
- package/telegram-plugin/dist/noise.mjs +63473 -2
package/LIESMICH.txt
CHANGED
|
@@ -145,5 +145,14 @@ Telegram einrichten
|
|
|
145
145
|
|
|
146
146
|
~/.blun/channels/telegram/.env
|
|
147
147
|
|
|
148
|
-
3. `king` starten und
|
|
149
|
-
|
|
148
|
+
3. `king` starten und in der Konsole eingeben:
|
|
149
|
+
|
|
150
|
+
/telegram:access connect
|
|
151
|
+
|
|
152
|
+
4. Den dort erzeugten Einmalcode im gewünschten privaten Chat oder in der
|
|
153
|
+
gewünschten Gruppe senden:
|
|
154
|
+
|
|
155
|
+
/connect CODE
|
|
156
|
+
|
|
157
|
+
Chat-ID und Absender-ID werden intern gespeichert und müssen nie eingegeben
|
|
158
|
+
werden. Der Code verfällt nach zehn Minuten und funktioniert nur einmal.
|
package/README.md
CHANGED
|
@@ -35,6 +35,11 @@ an. Version, Konto, Modell und Befehle sind ansonsten identisch. Der Unterschied
|
|
|
35
35
|
gilt nur für den laufenden Prozess; die gespeicherte
|
|
36
36
|
Plugin-Konfiguration wird nicht umgeschrieben.
|
|
37
37
|
|
|
38
|
+
Telegram-Chats werden ohne manuell eingegebene IDs verbunden. In der Konsole
|
|
39
|
+
erzeugt `/telegram:access connect` einen einmaligen Code. `/connect CODE` im
|
|
40
|
+
gewünschten privaten Chat oder in einer Gruppe speichert Chat und Absender
|
|
41
|
+
intern; der Code verfällt nach zehn Minuten und ist nur einmal verwendbar.
|
|
42
|
+
|
|
38
43
|
## Zuverlässiger King-Start
|
|
39
44
|
|
|
40
45
|
Der Windows-Hilfsprozess für private Pfade übernimmt `TEMP` und `TMP` aus der
|
|
@@ -106,6 +111,10 @@ besten sicheren lokalen nächsten Schritt um. Der sichtbare Aufgabenplan und all
|
|
|
106
111
|
Freigabegrenzen von `/idea` gelten weiterhin. Der Befehl funktioniert auch über
|
|
107
112
|
Telegram.
|
|
108
113
|
|
|
114
|
+
Der sichtbare Verlauf behält standardmäßig die vollständige laufende und
|
|
115
|
+
wiederhergestellte Sitzung. Das Mausrad kann den Verlauf direkt beim ersten Zug
|
|
116
|
+
öffnen; Editor und Fußzeile bleiben dabei fest sichtbar.
|
|
117
|
+
|
|
109
118
|
Im automatisch angebundenen Telegram-Kanal werden ausschließlich `/loop`,
|
|
110
119
|
`/goal`, `/idea`, `/chancenradar` und `/befehle` als Befehle ausgeführt. Ist der Agent
|
|
111
120
|
beschäftigt, bleiben sie in der Warteschlange. Andere Slash-Eingaben werden aus
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const DEFAULT_COMPACTION_TRIGGER_MAX_TOKENS = 256_000;
|
|
4
|
+
const DEFAULT_COMPACTION_BLOCK_MAX_TOKENS = 288_000;
|
|
5
|
+
|
|
6
|
+
function capCompactionThreshold(threshold, phase) {
|
|
7
|
+
if (threshold === undefined) return undefined;
|
|
8
|
+
const maximum = phase === "block"
|
|
9
|
+
? DEFAULT_COMPACTION_BLOCK_MAX_TOKENS
|
|
10
|
+
: DEFAULT_COMPACTION_TRIGGER_MAX_TOKENS;
|
|
11
|
+
return Math.min(threshold, maximum);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
module.exports = {
|
|
15
|
+
DEFAULT_COMPACTION_TRIGGER_MAX_TOKENS,
|
|
16
|
+
DEFAULT_COMPACTION_BLOCK_MAX_TOKENS,
|
|
17
|
+
capCompactionThreshold,
|
|
18
|
+
};
|
package/bin/launcher-runtime.js
CHANGED
|
@@ -18,7 +18,7 @@ const {
|
|
|
18
18
|
const { installManagedTelegramPlugin } = require('./plugin-bootstrap');
|
|
19
19
|
const {
|
|
20
20
|
availableStandardToolNames,
|
|
21
|
-
|
|
21
|
+
seedStandardSkills,
|
|
22
22
|
seedStandardTools,
|
|
23
23
|
} = require('./standard-tools-bootstrap');
|
|
24
24
|
const { CORE_LOADED_MESSAGE, RUNTIME_READY_MESSAGE } = require('./core-bootstrap');
|
|
@@ -552,7 +552,7 @@ async function runLauncher(options = {}) {
|
|
|
552
552
|
});
|
|
553
553
|
|
|
554
554
|
// --- 4. Eigenes BLUN-Grunddesign als einzigen Standard-Skill installieren
|
|
555
|
-
|
|
555
|
+
seedStandardSkills({ packageRoot: PKG, blunDir: privatePaths.sharedHome });
|
|
556
556
|
|
|
557
557
|
// --- 5. Telegram-Plugin + Token-Vorlage (nur King) -----------------------
|
|
558
558
|
if (mode === LAUNCHER_MODES.KING) {
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('node:fs');
|
|
4
|
+
const zlib = require('node:zlib');
|
|
5
|
+
|
|
6
|
+
function tarText(buffer, start, length) {
|
|
7
|
+
return buffer.subarray(start, start + length).toString('utf8').replace(/\0.*$/s, '').trim();
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function listTarGzPaths(tarballPath) {
|
|
11
|
+
const archive = zlib.gunzipSync(fs.readFileSync(tarballPath));
|
|
12
|
+
const paths = [];
|
|
13
|
+
let offset = 0;
|
|
14
|
+
while (offset + 512 <= archive.length) {
|
|
15
|
+
const header = archive.subarray(offset, offset + 512);
|
|
16
|
+
if (header.every((byte) => byte === 0)) break;
|
|
17
|
+
const name = tarText(header, 0, 100);
|
|
18
|
+
const prefix = tarText(header, 345, 155);
|
|
19
|
+
const sizeText = tarText(header, 124, 12);
|
|
20
|
+
const size = sizeText ? Number.parseInt(sizeText, 8) : 0;
|
|
21
|
+
if (!Number.isFinite(size)) throw new Error(`Ungueltige TAR-Groesse bei ${name || '[ohne Namen]'}`);
|
|
22
|
+
const fullName = [prefix, name].filter(Boolean).join('/').replaceAll('\\', '/');
|
|
23
|
+
if (fullName) paths.push(fullName);
|
|
24
|
+
offset += 512 + Math.ceil(size / 512) * 512;
|
|
25
|
+
}
|
|
26
|
+
return paths;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function isStablePackagePath(filePath) {
|
|
30
|
+
const normalized = String(filePath || '').replaceAll('\\', '/');
|
|
31
|
+
if (!normalized || normalized.endsWith('/')) return false;
|
|
32
|
+
if (normalized.startsWith('package/dist-web/assets/')) return false;
|
|
33
|
+
if (normalized.startsWith('package/dist-web/vis/')) return false;
|
|
34
|
+
return normalized.startsWith('package/');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function validatePlannedRemovals(document) {
|
|
38
|
+
const removals = Array.isArray(document?.removals) ? document.removals : [];
|
|
39
|
+
for (const removal of removals) {
|
|
40
|
+
if (!isStablePackagePath(removal?.path)) throw new Error(`Ungueltiger Entfernungspfad: ${removal?.path || '[leer]'}`);
|
|
41
|
+
if (!String(removal?.reason || '').trim()) throw new Error(`Entfernungsgrund fehlt: ${removal.path}`);
|
|
42
|
+
if (!String(removal?.decisionRef || '').trim()) throw new Error(`Entscheidungsreferenz fehlt: ${removal.path}`);
|
|
43
|
+
}
|
|
44
|
+
return new Set(removals.map((removal) => removal.path.replaceAll('\\', '/')));
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function findUnexpectedRemovals(previousPaths, currentPaths, plannedDocument = { removals: [] }) {
|
|
48
|
+
const current = new Set(currentPaths.map((entry) => String(entry).replaceAll('\\', '/')));
|
|
49
|
+
const planned = validatePlannedRemovals(plannedDocument);
|
|
50
|
+
return [...new Set(previousPaths
|
|
51
|
+
.map((entry) => String(entry).replaceAll('\\', '/'))
|
|
52
|
+
.filter(isStablePackagePath)
|
|
53
|
+
.filter((entry) => !current.has(entry) && !planned.has(entry)))]
|
|
54
|
+
.sort();
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function comparePackagePaths(previousPaths, currentPaths, plannedDocument = { removals: [] }) {
|
|
58
|
+
const previousStablePaths = [...new Set(previousPaths
|
|
59
|
+
.map((entry) => String(entry).replaceAll('\\', '/'))
|
|
60
|
+
.filter(isStablePackagePath))];
|
|
61
|
+
const currentStablePaths = [...new Set(currentPaths
|
|
62
|
+
.map((entry) => String(entry).replaceAll('\\', '/'))
|
|
63
|
+
.filter(isStablePackagePath))];
|
|
64
|
+
return {
|
|
65
|
+
checkedStablePaths: previousStablePaths.length,
|
|
66
|
+
currentStablePaths: currentStablePaths.length,
|
|
67
|
+
unexpectedRemovals: findUnexpectedRemovals(previousStablePaths, currentStablePaths, plannedDocument),
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
module.exports = {
|
|
72
|
+
comparePackagePaths,
|
|
73
|
+
findUnexpectedRemovals,
|
|
74
|
+
isStablePackagePath,
|
|
75
|
+
listTarGzPaths,
|
|
76
|
+
validatePlannedRemovals,
|
|
77
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const DEFAULT_PROVIDER_IDLE_TIMEOUT_MS = 30 * 1000;
|
|
4
|
+
|
|
5
|
+
function resolveProviderIdleTimeoutMs(configuredSeconds) {
|
|
6
|
+
return configuredSeconds === undefined
|
|
7
|
+
? DEFAULT_PROVIDER_IDLE_TIMEOUT_MS
|
|
8
|
+
: configuredSeconds * 1000;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
module.exports = {
|
|
12
|
+
DEFAULT_PROVIDER_IDLE_TIMEOUT_MS,
|
|
13
|
+
resolveProviderIdleTimeoutMs,
|
|
14
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const SKILL_DESCRIPTION_MAX_CHARS = 80;
|
|
4
|
+
const FIRST_USE_SKILLS = new Set([
|
|
5
|
+
'agent-browser',
|
|
6
|
+
'windows-mcp',
|
|
7
|
+
'translate-native',
|
|
8
|
+
'native-diacritics',
|
|
9
|
+
'blun-app-design-system',
|
|
10
|
+
]);
|
|
11
|
+
|
|
12
|
+
function compactDescription(value) {
|
|
13
|
+
const normalized = String(value || '').replace(/\s+/g, ' ').trim();
|
|
14
|
+
if (normalized.length <= SKILL_DESCRIPTION_MAX_CHARS) return normalized;
|
|
15
|
+
return `${normalized.slice(0, SKILL_DESCRIPTION_MAX_CHARS - 1).trimEnd()}...`;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function renderCompactSkillEntry(skill) {
|
|
19
|
+
const name = String(skill?.name || '').trim();
|
|
20
|
+
if (!FIRST_USE_SKILLS.has(name.toLowerCase())) return `- ${name}`;
|
|
21
|
+
const description = compactDescription(skill?.description);
|
|
22
|
+
return description ? `- ${name}: ${description}` : `- ${name}`;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function renderCompactSkillListing(skills) {
|
|
26
|
+
return (Array.isArray(skills) ? skills : []).map(renderCompactSkillEntry).join('\n');
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
module.exports = {
|
|
30
|
+
SKILL_DESCRIPTION_MAX_CHARS,
|
|
31
|
+
renderCompactSkillEntry,
|
|
32
|
+
renderCompactSkillListing,
|
|
33
|
+
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const { randomUUID } = require('node:crypto');
|
|
3
|
+
const { createHash, randomUUID } = require('node:crypto');
|
|
4
4
|
const fs = require('node:fs');
|
|
5
5
|
const path = require('node:path');
|
|
6
6
|
|
|
@@ -218,42 +218,96 @@ function seedStandardTools({
|
|
|
218
218
|
return { added, updated, configPath };
|
|
219
219
|
}
|
|
220
220
|
|
|
221
|
-
function
|
|
222
|
-
const
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
);
|
|
227
|
-
const sourceStat = fs.lstatSync(sourcePath);
|
|
228
|
-
if (!sourceStat.isDirectory() || sourceStat.isSymbolicLink()) {
|
|
229
|
-
throw new Error(`Ungueltiger BLUN-Standard-Skill: ${sourcePath}`);
|
|
221
|
+
function seedStandardSkills({ packageRoot, blunDir }) {
|
|
222
|
+
const sourceRoot = path.join(packageRoot, 'standard-skills');
|
|
223
|
+
const sourceRootStat = fs.lstatSync(sourceRoot);
|
|
224
|
+
if (!sourceRootStat.isDirectory() || sourceRootStat.isSymbolicLink()) {
|
|
225
|
+
throw new Error(`Ungueltiges BLUN-Standard-Skill-Verzeichnis: ${sourceRoot}`);
|
|
230
226
|
}
|
|
231
|
-
|
|
232
227
|
const skillsDir = path.join(blunDir, 'skills');
|
|
233
228
|
ensurePrivateDirectory(skillsDir);
|
|
234
|
-
const
|
|
235
|
-
const installed =
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
229
|
+
const markerPath = path.join(skillsDir, '.blun-standard-skills.json');
|
|
230
|
+
const installed = [];
|
|
231
|
+
const updated = [];
|
|
232
|
+
const destinationPaths = [];
|
|
233
|
+
|
|
234
|
+
const entries = fs.readdirSync(sourceRoot, { withFileTypes: true })
|
|
235
|
+
.filter((entry) => entry.isDirectory() && !entry.isSymbolicLink())
|
|
236
|
+
.sort((left, right) => left.name.localeCompare(right.name));
|
|
237
|
+
const fingerprint = createHash('sha256');
|
|
238
|
+
const visit = (directory, relative = '') => {
|
|
239
|
+
for (const entry of fs.readdirSync(directory, { withFileTypes: true })
|
|
240
|
+
.sort((left, right) => left.name.localeCompare(right.name))) {
|
|
241
|
+
const sourcePath = path.join(directory, entry.name);
|
|
242
|
+
const relativePath = path.join(relative, entry.name).replaceAll('\\', '/');
|
|
243
|
+
if (entry.isSymbolicLink()) throw new Error(`Symlink im BLUN-Standard-Skill: ${sourcePath}`);
|
|
244
|
+
fingerprint.update(relativePath);
|
|
245
|
+
if (entry.isDirectory()) visit(sourcePath, relativePath);
|
|
246
|
+
else if (entry.isFile()) fingerprint.update(fs.readFileSync(sourcePath));
|
|
247
|
+
else throw new Error(`Ungueltiger Eintrag im BLUN-Standard-Skill: ${sourcePath}`);
|
|
240
248
|
}
|
|
241
|
-
}
|
|
242
|
-
|
|
249
|
+
};
|
|
250
|
+
visit(sourceRoot);
|
|
251
|
+
const sourceFingerprint = fingerprint.digest('hex');
|
|
252
|
+
let marker = null;
|
|
253
|
+
try {
|
|
254
|
+
marker = JSON.parse(fs.readFileSync(markerPath, 'utf8'));
|
|
255
|
+
} catch {
|
|
256
|
+
marker = null;
|
|
243
257
|
}
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
258
|
+
const allDestinationsExist = entries.every((entry) => {
|
|
259
|
+
const destinationPath = path.join(skillsDir, entry.name);
|
|
260
|
+
destinationPaths.push(destinationPath);
|
|
261
|
+
if (!fs.existsSync(destinationPath)) return false;
|
|
262
|
+
const stat = fs.lstatSync(destinationPath);
|
|
263
|
+
return stat.isDirectory() && !stat.isSymbolicLink();
|
|
249
264
|
});
|
|
250
|
-
|
|
251
|
-
|
|
265
|
+
if (marker?.fingerprint === sourceFingerprint && allDestinationsExist) {
|
|
266
|
+
return { installed, updated, destinationPaths, unchanged: true };
|
|
267
|
+
}
|
|
268
|
+
destinationPaths.length = 0;
|
|
269
|
+
|
|
270
|
+
for (const entry of entries) {
|
|
271
|
+
const sourcePath = path.join(sourceRoot, entry.name);
|
|
272
|
+
const destinationPath = path.join(skillsDir, entry.name);
|
|
273
|
+
const isInstalled = !fs.existsSync(destinationPath);
|
|
274
|
+
if (!isInstalled) {
|
|
275
|
+
const destinationStat = fs.lstatSync(destinationPath);
|
|
276
|
+
if (!destinationStat.isDirectory() || destinationStat.isSymbolicLink()) {
|
|
277
|
+
throw new Error(`Ungueltiges Ziel fuer BLUN-Standard-Skill: ${destinationPath}`);
|
|
278
|
+
}
|
|
279
|
+
} else {
|
|
280
|
+
fs.mkdirSync(destinationPath, { mode: 0o700 });
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
fs.cpSync(sourcePath, destinationPath, {
|
|
284
|
+
recursive: true,
|
|
285
|
+
force: true,
|
|
286
|
+
errorOnExist: false,
|
|
287
|
+
});
|
|
288
|
+
(isInstalled ? installed : updated).push(entry.name);
|
|
289
|
+
destinationPaths.push(destinationPath);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
fs.writeFileSync(markerPath, `${JSON.stringify({ fingerprint: sourceFingerprint, skills: entries.map((entry) => entry.name) }, null, 2)}\n`, 'utf8');
|
|
293
|
+
if (process.platform !== 'win32') securePrivateTree(skillsDir);
|
|
294
|
+
return { installed, updated, destinationPaths, unchanged: false };
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
function seedStandardDesignSkill(options) {
|
|
298
|
+
const result = seedStandardSkills(options);
|
|
299
|
+
const name = 'blun-app-design-system';
|
|
300
|
+
return {
|
|
301
|
+
installed: result.installed.includes(name),
|
|
302
|
+
updated: result.updated.includes(name),
|
|
303
|
+
destinationPath: path.join(options.blunDir, 'skills', name),
|
|
304
|
+
};
|
|
252
305
|
}
|
|
253
306
|
|
|
254
307
|
module.exports = {
|
|
255
308
|
availableStandardToolNames,
|
|
256
309
|
readCatalogue,
|
|
257
310
|
seedStandardDesignSkill,
|
|
311
|
+
seedStandardSkills,
|
|
258
312
|
seedStandardTools,
|
|
259
313
|
};
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const EXPLICIT_THINKING_INTENT = /\b(?:analy[sz](?:e|ing|is)|root\s+cause|investigat(?:e|ion)|debug(?:ging)?|architect(?:ure|ural)|plan(?:ning)?|review|audit|threat\s+model|refactor|optim(?:ize|ise|ization)|migration|complex|thorough|deep(?:ly)?|untersuch(?:e|ung)|analysier(?:e|en)?|ursachenanalyse|architektur|plan(?:e|ung)|pruef(?:e|ung)|sicherheit|migration|komplex|gruendlich|analiz(?:a|iraj)|istrazi|arhitektur|planiraj|sigurnost|duboko)\b/iu;
|
|
4
|
+
const ACTIONABLE_PROMPT = /\b(?:aender|analys|apply|bau|build|check|commit|cop(?:y|ier)|create|debug|delete|deploy|edit|entfern|erstelle|find|fix|implement|install|lad|loesch|merge|mess|move|napravi|oeffne|open|patch|pruef|publish|push|read|reparier|restart|run|schreib|search|starte?|stop|such|test|update|verschieb|write|zieh)\w*\b/iu;
|
|
5
|
+
const STANDALONE_CHAT = /^(?:hi|hallo|hey|hello|hoi|moin|servus|yo|danke|thanks|thx|bye|tschau|ciao)[.!?]*$/iu;
|
|
6
|
+
const IDENTITY_CHAT = /^(?:wer bist du|who are you|wie geht(?: es dir)?|how are you|na)[.!?]*$/iu;
|
|
7
|
+
const STATUS_ONLY_CHECK_IN = /(?:\bstandabfrage\b|\bstatusabfrage\b|\bkurzes?\s+update\b|\bwo\s+stehst\s+du\b|\bwie\s+weit\s+bist\s+du\b|\bwhere\s+are\s+you\b|\bquick\s+status\b)/iu;
|
|
8
|
+
const STATUS_FOLLOW_UP_ACTION = /\b(?:aendere|arbeite|baue|build|change|delete|deploy|deploye|fahr|fix|fixe|implement|implementiere|install|installiere|loesche|mach|patch|pruefe|repariere|run|schreibe|setze|start|starte|stop|stoppe|test|teste|write)\b/iu;
|
|
9
|
+
const LIGHTWEIGHT_STEER = /\b(?:cool|danke|dankeschoen|gute arbeit|passt|perfekt|stolz|super|freut mich)\b/iu;
|
|
10
|
+
|
|
11
|
+
function selectThinkingEffortForTurn(text, originKind) {
|
|
12
|
+
if (originKind !== "user") return undefined;
|
|
13
|
+
|
|
14
|
+
const normalized = String(text ?? "").replace(/\s+/gu, " ").trim();
|
|
15
|
+
if (!normalized) return "low";
|
|
16
|
+
|
|
17
|
+
const lower = normalized.toLowerCase();
|
|
18
|
+
if (STANDALONE_CHAT.test(lower) || IDENTITY_CHAT.test(lower)) return "low";
|
|
19
|
+
if (
|
|
20
|
+
STATUS_ONLY_CHECK_IN.test(normalized)
|
|
21
|
+
&& !STATUS_FOLLOW_UP_ACTION.test(normalized)
|
|
22
|
+
&& !EXPLICIT_THINKING_INTENT.test(normalized)
|
|
23
|
+
) return "low";
|
|
24
|
+
if (normalized.length > 180 || String(text ?? "").split(/\r?\n/u).length > 2) return undefined;
|
|
25
|
+
if (ACTIONABLE_PROMPT.test(normalized) || EXPLICIT_THINKING_INTENT.test(normalized)) return undefined;
|
|
26
|
+
|
|
27
|
+
const shortQuestion = /[?]\s*$/u.test(normalized)
|
|
28
|
+
|| /^(?:was|wer|wie|warum|wieso|weshalb|wo|wann|welche?r?s?|what|who|how|why|where|when|which|can you|kannst du)\b/iu.test(normalized);
|
|
29
|
+
return shortQuestion ? "low" : undefined;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function shouldEnableThinkingForTurn(text, originKind) {
|
|
33
|
+
return selectThinkingEffortForTurn(text, originKind) !== "off";
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function selectThinkingEffortForWorkStep(stepNumber, previousToolOutcome) {
|
|
37
|
+
const normalizedStep = Number(stepNumber);
|
|
38
|
+
if (!Number.isInteger(normalizedStep) || normalizedStep <= 1) return undefined;
|
|
39
|
+
return previousToolOutcome === "success" ? "low" : undefined;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function selectThinkingEffortForBufferedSteer(text, originKind) {
|
|
43
|
+
const classified = selectThinkingEffortForTurn(text, originKind);
|
|
44
|
+
if (classified !== undefined) return classified;
|
|
45
|
+
if (originKind !== "user") return undefined;
|
|
46
|
+
|
|
47
|
+
const normalized = String(text ?? "").replace(/\s+/gu, " ").trim();
|
|
48
|
+
if (
|
|
49
|
+
!normalized
|
|
50
|
+
|| normalized.length > 180
|
|
51
|
+
|| String(text ?? "").split(/\r?\n/u).length > 2
|
|
52
|
+
|| ACTIONABLE_PROMPT.test(normalized)
|
|
53
|
+
|| EXPLICIT_THINKING_INTENT.test(normalized)
|
|
54
|
+
) return undefined;
|
|
55
|
+
|
|
56
|
+
return LIGHTWEIGHT_STEER.test(normalized) ? "low" : undefined;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
module.exports = {
|
|
60
|
+
ACTIONABLE_PROMPT,
|
|
61
|
+
EXPLICIT_THINKING_INTENT,
|
|
62
|
+
selectThinkingEffortForBufferedSteer,
|
|
63
|
+
selectThinkingEffortForWorkStep,
|
|
64
|
+
selectThinkingEffortForTurn,
|
|
65
|
+
shouldEnableThinkingForTurn,
|
|
66
|
+
};
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const TOOL_SCHEMA_BUDGET_RATIO = 0.25;
|
|
4
|
+
const TOOL_SCHEMA_MAX_TOKENS = 32_000;
|
|
5
|
+
const DEFERRED_TOOL_LOADER_NAME = 'ToolSearch';
|
|
6
|
+
const CORE_TOOL_NAMES = Object.freeze([
|
|
7
|
+
'Bash',
|
|
8
|
+
'Read',
|
|
9
|
+
'Edit',
|
|
10
|
+
'Grep',
|
|
11
|
+
'Write',
|
|
12
|
+
'TodoList',
|
|
13
|
+
'Glob',
|
|
14
|
+
'TaskList',
|
|
15
|
+
'Agent',
|
|
16
|
+
'GetGoal',
|
|
17
|
+
'CronList',
|
|
18
|
+
'ReadMediaFile',
|
|
19
|
+
'TaskOutput',
|
|
20
|
+
'Skill',
|
|
21
|
+
'mcp__plugin-telegram_telegram__reply',
|
|
22
|
+
'mcp__plugin-telegram_telegram__react',
|
|
23
|
+
'mcp__plugin-telegram_telegram__edit_message',
|
|
24
|
+
'mcp__plugin-telegram_telegram__download_attachment',
|
|
25
|
+
]);
|
|
26
|
+
|
|
27
|
+
function toolSchemaBudgetTokens(maxContextTokens) {
|
|
28
|
+
const context = Number(maxContextTokens);
|
|
29
|
+
if (!Number.isFinite(context) || context <= 0) return TOOL_SCHEMA_MAX_TOKENS;
|
|
30
|
+
return Math.min(TOOL_SCHEMA_MAX_TOKENS, Math.floor(context * TOOL_SCHEMA_BUDGET_RATIO));
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function deferredToolNames(tools) {
|
|
34
|
+
return [...new Set((Array.isArray(tools) ? tools : [])
|
|
35
|
+
.map((tool) => String(tool?.name || '').trim())
|
|
36
|
+
.filter(Boolean))].sort((left, right) => left.localeCompare(right));
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function searchDeferredTools(tools, query) {
|
|
40
|
+
const normalized = String(query || '').trim().toLowerCase();
|
|
41
|
+
if (!normalized) return [];
|
|
42
|
+
if (normalized.startsWith('select:')) {
|
|
43
|
+
const selector = normalized.slice('select:'.length).trim();
|
|
44
|
+
if (!selector) return [];
|
|
45
|
+
const exact = tools.find((tool) => String(tool?.name || '').toLowerCase() === selector);
|
|
46
|
+
if (exact) return [exact];
|
|
47
|
+
return tools.filter((tool) => {
|
|
48
|
+
const name = String(tool?.name || '').toLowerCase();
|
|
49
|
+
const leaf = name.split(/__|:/).at(-1);
|
|
50
|
+
return leaf === selector;
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const tokens = normalized.split(/\s+/).filter(Boolean);
|
|
55
|
+
const requiredNameTokens = tokens.filter((token) => token.startsWith('+')).map((token) => token.slice(1)).filter(Boolean);
|
|
56
|
+
const searchTokens = tokens.filter((token) => !token.startsWith('+'));
|
|
57
|
+
return tools.map((tool) => {
|
|
58
|
+
const name = String(tool?.name || '').toLowerCase();
|
|
59
|
+
const description = String(tool?.description || '').toLowerCase();
|
|
60
|
+
if (!requiredNameTokens.every((token) => name.includes(token))) return null;
|
|
61
|
+
if (!searchTokens.every((token) => name.includes(token) || description.includes(token))) return null;
|
|
62
|
+
const score = searchTokens.reduce((total, token) => total + (name.includes(token) ? 5 : 1), 0)
|
|
63
|
+
+ requiredNameTokens.length * 5;
|
|
64
|
+
return { tool, score };
|
|
65
|
+
}).filter(Boolean).sort((left, right) => right.score - left.score || String(left.tool.name).localeCompare(String(right.tool.name))).slice(0, 5).map((entry) => entry.tool);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function createDeferredToolLoader(selectedTools, deferredTools, loadedToolNames = new Set()) {
|
|
69
|
+
if (!Array.isArray(selectedTools)) throw new TypeError('selectedTools must be an array');
|
|
70
|
+
if (!(loadedToolNames instanceof Set)) throw new TypeError('loadedToolNames must be a Set');
|
|
71
|
+
const available = new Map();
|
|
72
|
+
for (const tool of Array.isArray(deferredTools) ? deferredTools : []) {
|
|
73
|
+
const name = String(tool?.name || '').trim();
|
|
74
|
+
if (name && !available.has(name)) available.set(name, tool);
|
|
75
|
+
}
|
|
76
|
+
const names = deferredToolNames([...available.values()]);
|
|
77
|
+
|
|
78
|
+
return {
|
|
79
|
+
name: DEFERRED_TOOL_LOADER_NAME,
|
|
80
|
+
description: [
|
|
81
|
+
'Find and load deferred tool schemas into this running session.',
|
|
82
|
+
'Use select:leaf_name for one known tool, plain keywords to search, or +word to require that word in the tool name.',
|
|
83
|
+
'Loaded tools are available in the next step and remain loaded for this session.',
|
|
84
|
+
`Available deferred tool names: ${names.join(', ')}`,
|
|
85
|
+
].join('\n'),
|
|
86
|
+
parameters: {
|
|
87
|
+
type: 'object',
|
|
88
|
+
properties: {
|
|
89
|
+
query: {
|
|
90
|
+
type: 'string',
|
|
91
|
+
description: 'Exact selection or keyword query, for example select:download_attachment or +slack send.',
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
required: ['query'],
|
|
95
|
+
additionalProperties: false,
|
|
96
|
+
},
|
|
97
|
+
resolveExecution(args) {
|
|
98
|
+
const query = String(args?.query || '').trim();
|
|
99
|
+
return {
|
|
100
|
+
description: query ? `Searching tool schemas for ${query}` : 'Searching tool schemas',
|
|
101
|
+
approvalRule: DEFERRED_TOOL_LOADER_NAME,
|
|
102
|
+
execute: async () => {
|
|
103
|
+
const matches = searchDeferredTools([...available.values()], query);
|
|
104
|
+
if (matches.length === 0) {
|
|
105
|
+
return {
|
|
106
|
+
isError: true,
|
|
107
|
+
output: `No deferred tools matched: ${query || '[empty query]'}`,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
if (query.toLowerCase().startsWith('select:') && matches.length !== 1) {
|
|
111
|
+
return {
|
|
112
|
+
isError: true,
|
|
113
|
+
output: `Tool selection is ambiguous: ${matches.map((tool) => tool.name).join(', ')}`,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
const loaded = [];
|
|
117
|
+
for (const tool of matches) {
|
|
118
|
+
const name = String(tool.name);
|
|
119
|
+
if (!selectedTools.some((candidate) => candidate?.name === name)) selectedTools.push(tool);
|
|
120
|
+
loadedToolNames.add(name);
|
|
121
|
+
loaded.push(name);
|
|
122
|
+
}
|
|
123
|
+
return {
|
|
124
|
+
isError: false,
|
|
125
|
+
output: `Loaded tool schemas: ${loaded.join(', ')}. Invoke them in the next step.`,
|
|
126
|
+
};
|
|
127
|
+
},
|
|
128
|
+
};
|
|
129
|
+
},
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
module.exports = {
|
|
134
|
+
CORE_TOOL_NAMES,
|
|
135
|
+
DEFERRED_TOOL_LOADER_NAME,
|
|
136
|
+
TOOL_SCHEMA_BUDGET_RATIO,
|
|
137
|
+
TOOL_SCHEMA_MAX_TOKENS,
|
|
138
|
+
createDeferredToolLoader,
|
|
139
|
+
deferredToolNames,
|
|
140
|
+
toolSchemaBudgetTokens,
|
|
141
|
+
};
|