blun-king-cli 9.1.615 → 9.1.616
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/CHANGELOG.md +5 -0
- package/LIESMICH.txt +1 -1
- package/README.md +1 -1
- package/bin/managed-plugin-selection.cjs +15 -8
- package/bin/plugin-bootstrap.js +40 -17
- package/bin/private-paths.js +10 -2
- package/bin/profile-launch.cjs +84 -6
- package/blun.mjs +1066 -565
- package/package.json +1 -1
- package/telegram-plugin/dist/bridge.mjs +53 -13
- package/telegram-plugin/dist/mcp-server.mjs +54 -10
- package/telegram-plugin/dist/noise.mjs +9 -1
- package/worker-host.mjs +272 -77
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
# BLUN King Changelog
|
|
2
2
|
|
|
3
|
+
## 9.1.616
|
|
4
|
+
|
|
5
|
+
- Nach einer bestätigten Telegram-Antwort übernimmt King weitere an dich adressierte Nachrichten am nächsten sicheren Schritt, auch während eines laufenden Zugs. Eine bereits zugestellte Antwort hält die Warteschlange nicht mehr auf. Antworten bleiben ihrem jeweiligen Chat zugeordnet.
|
|
6
|
+
- Brichst du einen Zug ab, bleiben noch nicht übernommene Telegram-Nachrichten für die nächste Zustellung erhalten. King quittiert sie erst, wenn die Sitzung sie tatsächlich übernimmt.
|
|
7
|
+
|
|
3
8
|
## 9.1.615
|
|
4
9
|
|
|
5
10
|
Große Werkzeugkataloge laufen jetzt über Kings vorhandene Werkzeugsuche, statt alle optionalen Werkzeugschemata auf einmal an das Modell zu senden. Meldet der Anbieter ein kleineres Kontextfenster, passt King das Werkzeugbudget an. Neu gefundene Werkzeuge bleiben für den nächsten Schritt verfügbar. Bei der automatischen Auswahl liest King keine Parameterschemata noch nicht ausgewählter MCP-Werkzeuge; lokal registrierte Werkzeuge behalten ihre Parameterhinweise.
|
package/LIESMICH.txt
CHANGED
package/README.md
CHANGED
|
@@ -5,8 +5,10 @@ const path = require('node:path');
|
|
|
5
5
|
const { ensurePrivateDirectory, securePrivateTree } = require('./private-paths');
|
|
6
6
|
const SCHEMA = 'blun.managed-plugin-selection/v1';
|
|
7
7
|
|
|
8
|
-
function snapshotPluginTree(root) {
|
|
9
|
-
const entries = [];
|
|
8
|
+
function snapshotPluginTree(root, expectedEntries = null) {
|
|
9
|
+
const entries = [];
|
|
10
|
+
const expectedByPath = expectedEntries === null ? null
|
|
11
|
+
: new Map(expectedEntries.map(entry => [entry.path, entry]));
|
|
10
12
|
const rootStat = fs.lstatSync(root);
|
|
11
13
|
if (rootStat.isSymbolicLink() || !rootStat.isDirectory()) return null;
|
|
12
14
|
const visit = (directory, relativeDirectory = '') => {
|
|
@@ -14,19 +16,24 @@ function snapshotPluginTree(root) {
|
|
|
14
16
|
.sort((left, right) => left.name < right.name ? -1 : left.name > right.name ? 1 : 0)) {
|
|
15
17
|
const relativePath = path.join(relativeDirectory, entry.name);
|
|
16
18
|
const absolutePath = path.join(directory, entry.name);
|
|
17
|
-
const stat = fs.lstatSync(absolutePath);
|
|
18
|
-
if (stat.isSymbolicLink()) return false;
|
|
19
|
-
|
|
19
|
+
const stat = fs.lstatSync(absolutePath);
|
|
20
|
+
if (stat.isSymbolicLink()) return false;
|
|
21
|
+
const expected = expectedByPath?.get(relativePath);
|
|
22
|
+
if (expectedByPath && !expected) return false;
|
|
23
|
+
if (stat.isDirectory()) {
|
|
24
|
+
if (expected && expected.kind !== 'directory') return false;
|
|
20
25
|
entries.push({ kind: 'directory', path: relativePath });
|
|
21
26
|
if (!visit(absolutePath, relativePath)) return false;
|
|
22
|
-
} else if (stat.isFile()) {
|
|
27
|
+
} else if (stat.isFile()) {
|
|
28
|
+
if (expected && (expected.kind !== 'file' || stat.size !== expected.content.length)) return false;
|
|
23
29
|
entries.push({ kind: 'file', path: relativePath, content: fs.readFileSync(absolutePath) });
|
|
24
30
|
} else return false;
|
|
25
31
|
}
|
|
26
32
|
return true;
|
|
27
33
|
};
|
|
28
|
-
|
|
29
|
-
|
|
34
|
+
if (!visit(root) || (expectedByPath && entries.length !== expectedByPath.size)) return null;
|
|
35
|
+
return entries;
|
|
36
|
+
}
|
|
30
37
|
|
|
31
38
|
function digestPluginEntries(entries) {
|
|
32
39
|
if (entries === null) throw new Error('Invalid managed plugin tree');
|
package/bin/plugin-bootstrap.js
CHANGED
|
@@ -3,7 +3,13 @@
|
|
|
3
3
|
const { randomUUID } = require('node:crypto');
|
|
4
4
|
const fs = require('node:fs');
|
|
5
5
|
const path = require('node:path');
|
|
6
|
-
const {
|
|
6
|
+
const {
|
|
7
|
+
selectedAgentSpineRoot,
|
|
8
|
+
describeAgentSpineSelection,
|
|
9
|
+
copySelectedAgentSpinePlugin,
|
|
10
|
+
snapshotPluginTree,
|
|
11
|
+
digestPluginEntries,
|
|
12
|
+
} = require('./managed-plugin-selection.cjs');
|
|
7
13
|
|
|
8
14
|
const {
|
|
9
15
|
ensurePrivateDirectory,
|
|
@@ -88,20 +94,37 @@ function installManagedTelegramPlugin({ packageRoot, blunDir, now = new Date().t
|
|
|
88
94
|
|
|
89
95
|
ensurePrivateDirectory(pluginsDir);
|
|
90
96
|
if (fs.existsSync(installedFile)) securePrivateFile(installedFile);
|
|
91
|
-
const installed = readInstalledPlugins(installedFile);
|
|
92
|
-
const updatedInstalled = updatedInstalledPlugins(installed, pluginRoot, now);
|
|
93
|
-
ensurePrivateDirectory(managedDir);
|
|
94
|
-
const
|
|
95
|
-
const
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
97
|
+
const installed = readInstalledPlugins(installedFile);
|
|
98
|
+
const updatedInstalled = updatedInstalledPlugins(installed, pluginRoot, now);
|
|
99
|
+
ensurePrivateDirectory(managedDir);
|
|
100
|
+
const expectedEntries = snapshotPluginTree(sourceRoot);
|
|
101
|
+
const expectedDigest = digestPluginEntries(expectedEntries);
|
|
102
|
+
let hadPreviousPlugin = false;
|
|
103
|
+
if (fs.existsSync(pluginRoot)) {
|
|
104
|
+
ensurePrivateDirectory(pluginRoot);
|
|
105
|
+
hadPreviousPlugin = true;
|
|
106
|
+
const existingEntries = snapshotPluginTree(pluginRoot, expectedEntries);
|
|
107
|
+
if (existingEntries !== null && digestPluginEntries(existingEntries) === expectedDigest) {
|
|
108
|
+
const previous = installed.plugins.find((plugin) => plugin.id === 'telegram');
|
|
109
|
+
const previousUpdate = typeof previous?.updatedAt === 'string' && previous.updatedAt.length > 0
|
|
110
|
+
? previous.updatedAt : now;
|
|
111
|
+
const reusedInstalled = updatedInstalledPlugins(installed, pluginRoot, previousUpdate);
|
|
112
|
+
if (JSON.stringify(reusedInstalled) !== JSON.stringify(installed)) {
|
|
113
|
+
writeInstalledPlugins(installedFile, reusedInstalled);
|
|
114
|
+
}
|
|
115
|
+
return { installedFile, pluginRoot, reused: true };
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
const nonce = `${process.pid}-${randomUUID()}`;
|
|
119
|
+
const stagingRoot = path.join(managedDir, `.telegram-${nonce}.tmp`);
|
|
120
|
+
const backupRoot = path.join(managedDir, `.telegram-${nonce}.bak`);
|
|
121
|
+
let installedNewPlugin = false;
|
|
122
|
+
try {
|
|
123
|
+
fs.cpSync(sourceRoot, stagingRoot, { recursive: true, force: true });
|
|
124
|
+
const stagedEntries = snapshotPluginTree(stagingRoot, expectedEntries);
|
|
125
|
+
if (stagedEntries === null || digestPluginEntries(stagedEntries) !== expectedDigest) {
|
|
126
|
+
throw new Error('Telegram source changed during installation');
|
|
127
|
+
}
|
|
105
128
|
if (hadPreviousPlugin) fs.renameSync(pluginRoot, backupRoot);
|
|
106
129
|
try {
|
|
107
130
|
fs.renameSync(stagingRoot, pluginRoot);
|
|
@@ -119,8 +142,8 @@ function installManagedTelegramPlugin({ packageRoot, blunDir, now = new Date().t
|
|
|
119
142
|
fs.rmSync(stagingRoot, { recursive: true, force: true });
|
|
120
143
|
}
|
|
121
144
|
|
|
122
|
-
return { installedFile, pluginRoot };
|
|
123
|
-
}
|
|
145
|
+
return { installedFile, pluginRoot, reused: false };
|
|
146
|
+
}
|
|
124
147
|
|
|
125
148
|
function installManagedAgentSpinePlugin({ packageRoot, blunDir, now = new Date().toISOString() }) {
|
|
126
149
|
const installedFile = path.join(blunDir, 'plugins', 'installed.json');
|
package/bin/private-paths.js
CHANGED
|
@@ -667,8 +667,16 @@ public static class BlunPrivateAclNative
|
|
|
667
667
|
VerifyHandlePath(handle, fullTarget);
|
|
668
668
|
HashSet<string> expectedSids = ExpectedSids(userSid);
|
|
669
669
|
string owner = ValidateOwner(handle, expectedSids);
|
|
670
|
-
|
|
671
|
-
|
|
670
|
+
// Avoid rewriting an exact directory DACL on every launcher start.
|
|
671
|
+
try
|
|
672
|
+
{
|
|
673
|
+
VerifyExactDacl(handle, userSid, true, owner);
|
|
674
|
+
}
|
|
675
|
+
catch (UnauthorizedAccessException)
|
|
676
|
+
{
|
|
677
|
+
SetExactDacl(handle, userSid, true);
|
|
678
|
+
VerifyExactDacl(handle, userSid, true, owner);
|
|
679
|
+
}
|
|
672
680
|
VerifyHandlePath(handle, fullTarget);
|
|
673
681
|
}
|
|
674
682
|
}
|
package/bin/profile-launch.cjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const path = require('node:path');
|
|
3
|
+
const path = require('node:path');
|
|
4
|
+
const fs = require('node:fs');
|
|
4
5
|
|
|
5
6
|
const PROFILE_NAME = /^[a-z0-9][a-z0-9_-]{0,31}$/u;
|
|
6
7
|
const RESERVED = new Set(['doctor', 'export', 'help', 'login', 'mcp', 'server', 'tools', 'update', 'upgrade', 'version']);
|
|
@@ -35,8 +36,85 @@ function parseProfileLaunchArgs(args, mode) {
|
|
|
35
36
|
return { args: remaining, name: undefined };
|
|
36
37
|
}
|
|
37
38
|
|
|
38
|
-
function resolveSelectedProfile(sharedHome, name, pathImpl = path) {
|
|
39
|
-
return pathImpl.join(sharedHome, 'profile', profileName(name));
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
|
|
39
|
+
function resolveSelectedProfile(sharedHome, name, pathImpl = path) {
|
|
40
|
+
return pathImpl.join(sharedHome, 'profile', profileName(name));
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function catalogUnavailable() {
|
|
44
|
+
return Object.assign(new Error('PROFILE_CATALOG_UNAVAILABLE'), { code: 'PROFILE_CATALOG_UNAVAILABLE' });
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function directoryIdentity(target, fsImpl) {
|
|
48
|
+
let stat;
|
|
49
|
+
try { stat = fsImpl.lstatSync(target, { bigint: true }); }
|
|
50
|
+
catch (error) {
|
|
51
|
+
if (error.code === 'ENOENT') return undefined;
|
|
52
|
+
throw error;
|
|
53
|
+
}
|
|
54
|
+
if (!stat.isDirectory() || stat.isSymbolicLink()) throw catalogUnavailable();
|
|
55
|
+
return stat;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function homeIdentity(home, fsImpl) {
|
|
59
|
+
const root = path.parse(home).root;
|
|
60
|
+
let current = root;
|
|
61
|
+
let stat = directoryIdentity(current, fsImpl);
|
|
62
|
+
for (const part of home.slice(root.length).split(path.sep).filter(Boolean)) {
|
|
63
|
+
if (stat === undefined) return undefined;
|
|
64
|
+
current = path.join(current, part);
|
|
65
|
+
stat = directoryIdentity(current, fsImpl);
|
|
66
|
+
}
|
|
67
|
+
return stat;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function sameDirectory(before, after) {
|
|
71
|
+
return before !== undefined && after !== undefined && before.dev === after.dev && before.ino === after.ino;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** Directory names only; selection must revalidate them before changing owner. */
|
|
75
|
+
function listExistingProfiles(sharedHome, options = {}) {
|
|
76
|
+
const fsImpl = options.fsImpl || fs;
|
|
77
|
+
try {
|
|
78
|
+
if (typeof sharedHome !== 'string' || !path.isAbsolute(sharedHome)) throw catalogUnavailable();
|
|
79
|
+
const home = path.resolve(sharedHome);
|
|
80
|
+
if (process.platform === 'win32' && home.startsWith('\\\\')) throw catalogUnavailable();
|
|
81
|
+
const beforeHome = homeIdentity(home, fsImpl);
|
|
82
|
+
if (beforeHome === undefined) return [];
|
|
83
|
+
const profileRoot = path.join(home, 'profile');
|
|
84
|
+
const beforeProfiles = directoryIdentity(profileRoot, fsImpl);
|
|
85
|
+
if (beforeProfiles === undefined) {
|
|
86
|
+
if (!sameDirectory(beforeHome, homeIdentity(home, fsImpl))) throw catalogUnavailable();
|
|
87
|
+
return [];
|
|
88
|
+
}
|
|
89
|
+
const rows = [];
|
|
90
|
+
const dir = fsImpl.opendirSync(profileRoot);
|
|
91
|
+
try {
|
|
92
|
+
let count = 0;
|
|
93
|
+
for (let entry; (entry = dir.readSync()) !== null;) {
|
|
94
|
+
if (++count > 4096) throw catalogUnavailable();
|
|
95
|
+
const name = entry.name;
|
|
96
|
+
if (!PROFILE_NAME.test(name)) continue;
|
|
97
|
+
const target = resolveSelectedProfile(home, name);
|
|
98
|
+
let stat;
|
|
99
|
+
try { stat = fsImpl.lstatSync(target, { bigint: true }); }
|
|
100
|
+
catch (error) {
|
|
101
|
+
if (error.code === 'ENOENT') continue;
|
|
102
|
+
throw error;
|
|
103
|
+
}
|
|
104
|
+
if (stat.isDirectory() && !stat.isSymbolicLink()) rows.push({ name, target, stat });
|
|
105
|
+
}
|
|
106
|
+
} finally { dir.closeSync(); }
|
|
107
|
+
if (!sameDirectory(beforeHome, homeIdentity(home, fsImpl))
|
|
108
|
+
|| !sameDirectory(beforeProfiles, directoryIdentity(profileRoot, fsImpl))) throw catalogUnavailable();
|
|
109
|
+
for (const row of rows) {
|
|
110
|
+
if (!sameDirectory(row.stat, directoryIdentity(row.target, fsImpl))) throw catalogUnavailable();
|
|
111
|
+
}
|
|
112
|
+
if (!sameDirectory(beforeHome, homeIdentity(home, fsImpl))
|
|
113
|
+
|| !sameDirectory(beforeProfiles, directoryIdentity(profileRoot, fsImpl))) throw catalogUnavailable();
|
|
114
|
+
return rows.map(row => row.name).sort();
|
|
115
|
+
} catch {
|
|
116
|
+
throw catalogUnavailable();
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
module.exports = { parseProfileLaunchArgs, resolveSelectedProfile, listExistingProfiles };
|