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 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
@@ -26,7 +26,7 @@ Installation
26
26
  ------------
27
27
  Die geprüfte Version exakt global installieren:
28
28
 
29
- npm install -g blun-king-cli@9.1.615
29
+ npm install -g blun-king-cli@9.1.616
30
30
 
31
31
  Start
32
32
  -----
package/README.md CHANGED
@@ -9,7 +9,7 @@ Voraussetzung ist Node.js 24.15 oder neuer. Die geprüfte Version wird exakt
9
9
  installiert:
10
10
 
11
11
  ```powershell
12
- npm install -g blun-king-cli@9.1.615
12
+ npm install -g blun-king-cli@9.1.616
13
13
  ```
14
14
 
15
15
  ### Windows: Node.js, npm und Git Bash
@@ -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
- if (stat.isDirectory()) {
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
- return visit(root) ? entries : null;
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');
@@ -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 { selectedAgentSpineRoot, describeAgentSpineSelection, copySelectedAgentSpinePlugin } = require('./managed-plugin-selection.cjs');
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 nonce = `${process.pid}-${randomUUID()}`;
95
- const stagingRoot = path.join(managedDir, `.telegram-${nonce}.tmp`);
96
- const backupRoot = path.join(managedDir, `.telegram-${nonce}.bak`);
97
- let hadPreviousPlugin = false;
98
- let installedNewPlugin = false;
99
- try {
100
- if (fs.existsSync(pluginRoot)) {
101
- ensurePrivateDirectory(pluginRoot);
102
- hadPreviousPlugin = true;
103
- }
104
- fs.cpSync(sourceRoot, stagingRoot, { recursive: true, force: true });
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');
@@ -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
- SetExactDacl(handle, userSid, true);
671
- VerifyExactDacl(handle, userSid, true, owner);
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
  }
@@ -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
- module.exports = { parseProfileLaunchArgs, resolveSelectedProfile };
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 };