blun-king-cli 9.1.628-pruefpaket.modules.r7 → 9.1.628
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/launcher-runtime.js +2 -1
- package/bin/managed-plugin-selection.cjs +116 -115
- package/bin/plugin-bootstrap.js +163 -162
- package/bin/rename-private-staging.cjs +82 -0
- package/blun.mjs +2 -2
- package/package.json +1 -1
- package/worker-host.mjs +2 -2
package/bin/launcher-runtime.js
CHANGED
|
@@ -40,6 +40,7 @@ const {
|
|
|
40
40
|
writePrivateFile,
|
|
41
41
|
} = require('./private-paths');
|
|
42
42
|
|
|
43
|
+
const { writeTelegramTemplate } = require('./rename-private-staging.cjs');
|
|
43
44
|
const PKG = path.resolve(__dirname, '..');
|
|
44
45
|
const ARGS = process.argv.slice(2);
|
|
45
46
|
|
|
@@ -253,7 +254,7 @@ function installTelegramForProfile(packageRoot, blunDir) {
|
|
|
253
254
|
ensurePrivateDirectory(channelDir);
|
|
254
255
|
const envFile = path.join(channelDir, '.env');
|
|
255
256
|
if (!fs.existsSync(envFile)) {
|
|
256
|
-
|
|
257
|
+
writeTelegramTemplate(envFile, 'BLUN_TELEGRAM_BOT_TOKEN=HIER_DEIN_BOT_TOKEN_EINFUEGEN\n');
|
|
257
258
|
} else {
|
|
258
259
|
securePrivateFile(envFile);
|
|
259
260
|
}
|
|
@@ -1,115 +1,116 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
const { createHash, randomUUID } = require('node:crypto');
|
|
3
|
-
const fs = require('node:fs');
|
|
4
|
-
const path = require('node:path');
|
|
5
|
-
const {
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
const
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
if (stat.
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
length.
|
|
39
|
-
|
|
40
|
-
hash.update(
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
const
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
if (records.
|
|
76
|
-
|
|
77
|
-
const
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
const
|
|
90
|
-
|
|
91
|
-
ensurePrivateDirectory(
|
|
92
|
-
|
|
93
|
-
const
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
1
|
+
'use strict';
|
|
2
|
+
const { createHash, randomUUID } = require('node:crypto');
|
|
3
|
+
const fs = require('node:fs');
|
|
4
|
+
const path = require('node:path');
|
|
5
|
+
const { renamePrivateStaging } = require('./rename-private-staging.cjs');
|
|
6
|
+
const { ensurePrivateDirectory, securePrivateTree } = require('./private-paths');
|
|
7
|
+
const SCHEMA = 'blun.managed-plugin-selection/v1';
|
|
8
|
+
|
|
9
|
+
function snapshotPluginTree(root) {
|
|
10
|
+
const entries = [];
|
|
11
|
+
const rootStat = fs.lstatSync(root);
|
|
12
|
+
if (rootStat.isSymbolicLink() || !rootStat.isDirectory()) return null;
|
|
13
|
+
const visit = (directory, relativeDirectory = '') => {
|
|
14
|
+
for (const entry of fs.readdirSync(directory, { withFileTypes: true })
|
|
15
|
+
.sort((left, right) => left.name < right.name ? -1 : left.name > right.name ? 1 : 0)) {
|
|
16
|
+
const relativePath = path.join(relativeDirectory, entry.name);
|
|
17
|
+
const absolutePath = path.join(directory, entry.name);
|
|
18
|
+
const stat = fs.lstatSync(absolutePath);
|
|
19
|
+
if (stat.isSymbolicLink()) return false;
|
|
20
|
+
if (stat.isDirectory()) {
|
|
21
|
+
entries.push({ kind: 'directory', path: relativePath });
|
|
22
|
+
if (!visit(absolutePath, relativePath)) return false;
|
|
23
|
+
} else if (stat.isFile()) {
|
|
24
|
+
entries.push({ kind: 'file', path: relativePath, content: fs.readFileSync(absolutePath) });
|
|
25
|
+
} else return false;
|
|
26
|
+
}
|
|
27
|
+
return true;
|
|
28
|
+
};
|
|
29
|
+
return visit(root) ? entries : null;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function digestPluginEntries(entries) {
|
|
33
|
+
if (entries === null) throw new Error('Invalid managed plugin tree');
|
|
34
|
+
const hash = createHash('sha256');
|
|
35
|
+
for (const entry of entries) {
|
|
36
|
+
hash.update(Buffer.from(`${entry.kind}\0${entry.path}\0`, 'utf8'));
|
|
37
|
+
if (entry.kind === 'file') {
|
|
38
|
+
const length = Buffer.allocUnsafe(8);
|
|
39
|
+
length.writeBigUInt64BE(BigInt(entry.content.length));
|
|
40
|
+
hash.update(length);
|
|
41
|
+
hash.update(entry.content);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return hash.digest('hex');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function samePath(left, right) {
|
|
48
|
+
const normalize = value => process.platform === 'win32' ? path.resolve(value).toLowerCase() : path.resolve(value);
|
|
49
|
+
return normalize(left) === normalize(right);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function scopedPluginRoot(blunDir, pluginRoot) {
|
|
53
|
+
const managed = path.resolve(blunDir, 'plugins', 'managed');
|
|
54
|
+
const root = path.resolve(pluginRoot);
|
|
55
|
+
if (!samePath(path.dirname(root), managed)) throw new Error('Selected AgentSpine root is outside this profile');
|
|
56
|
+
for (const directory of [blunDir, path.dirname(managed), managed, root]) {
|
|
57
|
+
const stat = fs.lstatSync(directory);
|
|
58
|
+
if (stat.isSymbolicLink() || !stat.isDirectory()) throw new Error('Selected AgentSpine root contains a linked directory');
|
|
59
|
+
}
|
|
60
|
+
if (!samePath(path.dirname(fs.realpathSync(root)), fs.realpathSync(managed))) {
|
|
61
|
+
throw new Error('Selected AgentSpine root resolves outside this profile');
|
|
62
|
+
}
|
|
63
|
+
return root;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function describeAgentSpineSelection({ blunDir, pluginRoot }) {
|
|
67
|
+
const root = scopedPluginRoot(blunDir, pluginRoot);
|
|
68
|
+
const digest = digestPluginEntries(snapshotPluginTree(root));
|
|
69
|
+
if (path.basename(root) !== `agent-spine-${digest}`) throw new Error('Selected AgentSpine digest mismatch');
|
|
70
|
+
return { schema: SCHEMA, digest };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function selectedAgentSpineRoot({ installed, blunDir }) {
|
|
74
|
+
const records = installed.plugins.filter(record => record.id === 'agent-spine');
|
|
75
|
+
if (!records.some(record => Object.hasOwn(record, 'managedSelection'))) return null;
|
|
76
|
+
if (records.length !== 1) throw new Error('Ambiguous AgentSpine selection');
|
|
77
|
+
const [record] = records;
|
|
78
|
+
const selection = record.managedSelection;
|
|
79
|
+
if (!selection || selection.schema !== SCHEMA || !/^[a-f0-9]{64}$/.test(selection.digest || '')
|
|
80
|
+
|| Object.keys(selection).sort().join(',') !== 'digest,schema' || typeof record.root !== 'string') {
|
|
81
|
+
throw new Error('Invalid AgentSpine selection');
|
|
82
|
+
}
|
|
83
|
+
const actual = describeAgentSpineSelection({ blunDir, pluginRoot: record.root });
|
|
84
|
+
if (actual.digest !== selection.digest) throw new Error('Selected AgentSpine digest mismatch');
|
|
85
|
+
return record.root;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function copySelectedAgentSpinePlugin({ blunDir, sourceRoot }) {
|
|
89
|
+
const plugins = path.resolve(blunDir, 'plugins');
|
|
90
|
+
const managed = path.join(plugins, 'managed');
|
|
91
|
+
ensurePrivateDirectory(plugins);
|
|
92
|
+
ensurePrivateDirectory(managed);
|
|
93
|
+
const digest = digestPluginEntries(snapshotPluginTree(sourceRoot));
|
|
94
|
+
const pluginRoot = path.join(managed, `agent-spine-${digest}`);
|
|
95
|
+
if (fs.existsSync(pluginRoot)) {
|
|
96
|
+
describeAgentSpineSelection({ blunDir, pluginRoot });
|
|
97
|
+
return pluginRoot;
|
|
98
|
+
}
|
|
99
|
+
const staging = path.join(managed, `.agent-spine-selected-${process.pid}-${randomUUID()}.tmp`);
|
|
100
|
+
try {
|
|
101
|
+
fs.cpSync(sourceRoot, staging, { recursive: true, errorOnExist: true, force: false });
|
|
102
|
+
securePrivateTree(staging);
|
|
103
|
+
if (digestPluginEntries(snapshotPluginTree(staging)) !== digest) throw new Error('AgentSpine source changed during installation');
|
|
104
|
+
renamePrivateStaging(staging, pluginRoot);
|
|
105
|
+
describeAgentSpineSelection({ blunDir, pluginRoot });
|
|
106
|
+
return pluginRoot;
|
|
107
|
+
} finally {
|
|
108
|
+
if (!samePath(path.dirname(staging), managed) || !path.basename(staging).startsWith('.agent-spine-selected-')) {
|
|
109
|
+
throw new Error('Invalid AgentSpine staging cleanup path');
|
|
110
|
+
}
|
|
111
|
+
fs.rmSync(staging, { recursive: true, force: true });
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
module.exports = { snapshotPluginTree, digestPluginEntries, describeAgentSpineSelection,
|
|
116
|
+
selectedAgentSpineRoot, copySelectedAgentSpinePlugin };
|
package/bin/plugin-bootstrap.js
CHANGED
|
@@ -1,162 +1,163 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const { randomUUID } = require('node:crypto');
|
|
4
|
-
const fs = require('node:fs');
|
|
5
|
-
const path = require('node:path');
|
|
6
|
-
const {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
const
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
const
|
|
85
|
-
const
|
|
86
|
-
const
|
|
87
|
-
const
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
const
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
const
|
|
96
|
-
const
|
|
97
|
-
|
|
98
|
-
let
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
if (
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
const
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
if (
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
const
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { randomUUID } = require('node:crypto');
|
|
4
|
+
const fs = require('node:fs');
|
|
5
|
+
const path = require('node:path');
|
|
6
|
+
const { renamePrivateStaging, copyTelegramFiles } = require('./rename-private-staging.cjs');
|
|
7
|
+
const { selectedAgentSpineRoot, describeAgentSpineSelection, copySelectedAgentSpinePlugin } = require('./managed-plugin-selection.cjs');
|
|
8
|
+
|
|
9
|
+
const {
|
|
10
|
+
ensurePrivateDirectory,
|
|
11
|
+
securePrivateFile,
|
|
12
|
+
writePrivateFile,
|
|
13
|
+
} = require('./private-paths');
|
|
14
|
+
|
|
15
|
+
function isObject(value) {
|
|
16
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function readInstalledPlugins(filePath) {
|
|
20
|
+
if (!fs.existsSync(filePath)) return { version: 1, plugins: [] };
|
|
21
|
+
|
|
22
|
+
const parsed = JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
23
|
+
if (!isObject(parsed) || parsed.version !== 1 || !Array.isArray(parsed.plugins)
|
|
24
|
+
|| !parsed.plugins.every(isObject)) {
|
|
25
|
+
throw new Error(`Ungültige Plugin-Konfiguration: ${filePath}`);
|
|
26
|
+
}
|
|
27
|
+
return parsed;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function updatedInstalledPlugins(installed, pluginRoot, now, pluginId = 'telegram') {
|
|
31
|
+
const previous = installed.plugins.find((plugin) => plugin.id === pluginId);
|
|
32
|
+
const previousCapabilities = isObject(previous?.capabilities) ? previous.capabilities : {};
|
|
33
|
+
const previousMcpServers = isObject(previousCapabilities.mcpServers)
|
|
34
|
+
? previousCapabilities.mcpServers : {};
|
|
35
|
+
const previousTelegramMcp = isObject(previousMcpServers[pluginId])
|
|
36
|
+
? previousMcpServers[pluginId] : {};
|
|
37
|
+
const telegram = {
|
|
38
|
+
...previous,
|
|
39
|
+
id: pluginId,
|
|
40
|
+
root: pluginRoot,
|
|
41
|
+
source: 'local-path',
|
|
42
|
+
enabled: true,
|
|
43
|
+
installedAt: typeof previous?.installedAt === 'string' && previous.installedAt.length > 0
|
|
44
|
+
? previous.installedAt : now,
|
|
45
|
+
updatedAt: now,
|
|
46
|
+
capabilities: {
|
|
47
|
+
...previousCapabilities,
|
|
48
|
+
mcpServers: {
|
|
49
|
+
...previousMcpServers,
|
|
50
|
+
[pluginId]: { ...previousTelegramMcp, enabled: previousTelegramMcp.enabled === false && pluginId === 'agent-spine' ? false : true },
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const plugins = [];
|
|
56
|
+
let replaced = false;
|
|
57
|
+
for (const plugin of installed.plugins) {
|
|
58
|
+
if (plugin.id !== pluginId) {
|
|
59
|
+
plugins.push(plugin);
|
|
60
|
+
} else if (!replaced) {
|
|
61
|
+
plugins.push(telegram);
|
|
62
|
+
replaced = true;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
if (!replaced) plugins.push(telegram);
|
|
66
|
+
return { ...installed, version: 1, plugins };
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function writeInstalledPlugins(filePath, installed) {
|
|
70
|
+
const temporaryPath = path.join(
|
|
71
|
+
path.dirname(filePath),
|
|
72
|
+
`.${path.basename(filePath)}.${process.pid}.${randomUUID()}.tmp`,
|
|
73
|
+
);
|
|
74
|
+
try {
|
|
75
|
+
writePrivateFile(temporaryPath, `${JSON.stringify(installed, null, 2)}\n`);
|
|
76
|
+
fs.renameSync(temporaryPath, filePath);
|
|
77
|
+
} catch (error) {
|
|
78
|
+
fs.rmSync(temporaryPath, { force: true });
|
|
79
|
+
throw error;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function installManagedTelegramPlugin({ packageRoot, blunDir, now = new Date().toISOString() }) {
|
|
84
|
+
const sourceRoot = path.join(packageRoot, 'telegram-plugin');
|
|
85
|
+
const pluginsDir = path.join(blunDir, 'plugins');
|
|
86
|
+
const managedDir = path.join(pluginsDir, 'managed');
|
|
87
|
+
const pluginRoot = path.join(managedDir, 'telegram');
|
|
88
|
+
const installedFile = path.join(pluginsDir, 'installed.json');
|
|
89
|
+
|
|
90
|
+
ensurePrivateDirectory(pluginsDir);
|
|
91
|
+
if (fs.existsSync(installedFile)) securePrivateFile(installedFile);
|
|
92
|
+
const installed = readInstalledPlugins(installedFile);
|
|
93
|
+
const updatedInstalled = updatedInstalledPlugins(installed, pluginRoot, now);
|
|
94
|
+
ensurePrivateDirectory(managedDir);
|
|
95
|
+
const nonce = `${process.pid}-${randomUUID()}`;
|
|
96
|
+
const stagingRoot = path.join(managedDir, `.telegram-${nonce}.tmp`);
|
|
97
|
+
const backupRoot = path.join(managedDir, `.telegram-${nonce}.bak`);
|
|
98
|
+
let hadPreviousPlugin = false;
|
|
99
|
+
let installedNewPlugin = false;
|
|
100
|
+
try {
|
|
101
|
+
if (fs.existsSync(pluginRoot)) {
|
|
102
|
+
ensurePrivateDirectory(pluginRoot);
|
|
103
|
+
hadPreviousPlugin = true;
|
|
104
|
+
}
|
|
105
|
+
copyTelegramFiles(sourceRoot, stagingRoot);
|
|
106
|
+
if (hadPreviousPlugin) fs.renameSync(pluginRoot, backupRoot);
|
|
107
|
+
try {
|
|
108
|
+
renamePrivateStaging(stagingRoot, pluginRoot);
|
|
109
|
+
installedNewPlugin = true;
|
|
110
|
+
writeInstalledPlugins(installedFile, updatedInstalled);
|
|
111
|
+
} catch (error) {
|
|
112
|
+
if (installedNewPlugin) fs.rmSync(pluginRoot, { recursive: true, force: true });
|
|
113
|
+
if (hadPreviousPlugin && fs.existsSync(backupRoot)) {
|
|
114
|
+
fs.renameSync(backupRoot, pluginRoot);
|
|
115
|
+
}
|
|
116
|
+
throw error;
|
|
117
|
+
}
|
|
118
|
+
if (hadPreviousPlugin) fs.rmSync(backupRoot, { recursive: true, force: true });
|
|
119
|
+
} finally {
|
|
120
|
+
fs.rmSync(stagingRoot, { recursive: true, force: true });
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return { installedFile, pluginRoot };
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function installManagedAgentSpinePlugin({ packageRoot, blunDir, now = new Date().toISOString() }) {
|
|
127
|
+
const installedFile = path.join(blunDir, 'plugins', 'installed.json');
|
|
128
|
+
ensurePrivateDirectory(path.dirname(installedFile));
|
|
129
|
+
if (fs.existsSync(installedFile)) securePrivateFile(installedFile);
|
|
130
|
+
const installed = readInstalledPlugins(installedFile);
|
|
131
|
+
const records = installed.plugins.filter(plugin => plugin.id === 'agent-spine');
|
|
132
|
+
if (records.length > 1) throw new Error('Ambiguous AgentSpine registration');
|
|
133
|
+
const selected = selectedAgentSpineRoot({ installed, blunDir });
|
|
134
|
+
if (selected) return { installedFile, pluginRoot: selected, reused: true, selected: true };
|
|
135
|
+
if (records[0]?.enabled === false) {
|
|
136
|
+
return { installedFile, pluginRoot: records[0].root, reused: true, disabled: true };
|
|
137
|
+
}
|
|
138
|
+
if (records[0]) {
|
|
139
|
+
const currentRoot = records[0].root;
|
|
140
|
+
if (typeof currentRoot !== 'string' || !path.isAbsolute(currentRoot)) {
|
|
141
|
+
throw new Error('Invalid AgentSpine registration root');
|
|
142
|
+
}
|
|
143
|
+
const managed = path.resolve(blunDir, 'plugins', 'managed');
|
|
144
|
+
const sameDirectory = (left, right) => process.platform === 'win32'
|
|
145
|
+
? left.toLowerCase() === right.toLowerCase() : left === right;
|
|
146
|
+
if (!sameDirectory(path.dirname(path.resolve(currentRoot)), managed)
|
|
147
|
+
|| !/^agent-spine-[a-f0-9]{64}$/.test(path.basename(currentRoot))) {
|
|
148
|
+
return { installedFile, pluginRoot: currentRoot, reused: true, custom: true };
|
|
149
|
+
}
|
|
150
|
+
// An upgrade must not conceal changes to the previous immutable default.
|
|
151
|
+
describeAgentSpineSelection({ blunDir, pluginRoot: currentRoot });
|
|
152
|
+
}
|
|
153
|
+
const pluginRoot = copySelectedAgentSpinePlugin({ blunDir, sourceRoot: path.join(packageRoot, 'agent-spine-plugin') });
|
|
154
|
+
if (records[0]?.root === pluginRoot) return { installedFile, pluginRoot, reused: true };
|
|
155
|
+
writeInstalledPlugins(installedFile, updatedInstalledPlugins(installed, pluginRoot, now, 'agent-spine'));
|
|
156
|
+
return { installedFile, pluginRoot, reused: false };
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
module.exports = {
|
|
160
|
+
installManagedAgentSpinePlugin,
|
|
161
|
+
installManagedTelegramPlugin,
|
|
162
|
+
readInstalledPlugins,
|
|
163
|
+
};
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
const fs = require('node:fs');
|
|
3
|
+
const path = require('node:path');
|
|
4
|
+
const pause = new Int32Array(new SharedArrayBuffer(4));
|
|
5
|
+
const RETRY_CODES = new Set(['EPERM', 'EBUSY', 'EACCES']);
|
|
6
|
+
function withWindowsFsRetry(operation, options = {}) {
|
|
7
|
+
if ((options.platform || process.platform) !== 'win32') return operation();
|
|
8
|
+
const now = options.now || (() => Number(process.hrtime.bigint()) / 1e6);
|
|
9
|
+
const sleep = options.sleep || (ms => Atomics.wait(pause, 0, 0, ms));
|
|
10
|
+
const started = now();
|
|
11
|
+
let firstError;
|
|
12
|
+
for (let attempt = 1; ; attempt++) {
|
|
13
|
+
if (options.validate) options.validate(firstError);
|
|
14
|
+
try { return operation(); } catch (error) {
|
|
15
|
+
if (!RETRY_CODES.has(error.code)) throw error;
|
|
16
|
+
firstError ||= error;
|
|
17
|
+
const elapsedMs = now() - started;
|
|
18
|
+
if (attempt >= 11 || elapsedMs >= 500) {
|
|
19
|
+
firstError.blunFsRetry = { attempts: attempt, elapsedMs, operation: options.label || 'Dateisystemoperation' };
|
|
20
|
+
firstError.message += ' [BLUN: ' + (options.label || 'Dateisystemoperation')
|
|
21
|
+
+ ' nach ' + attempt + ' Versuchen endgueltig fehlgeschlagen; Originalfehler bleibt erhalten]';
|
|
22
|
+
throw firstError;
|
|
23
|
+
}
|
|
24
|
+
sleep(Math.min(50, 500 - elapsedMs));
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
function canonical(value) { return path.resolve(value).toLowerCase(); }
|
|
29
|
+
function stagingState(staging, impl, expected) {
|
|
30
|
+
const parent = path.dirname(path.resolve(staging));
|
|
31
|
+
if (path.basename(parent) !== 'managed'
|
|
32
|
+
|| !/^\.(?:agent-spine-selected|telegram)-[0-9]+-[a-f0-9-]+\.tmp$/.test(path.basename(staging))
|
|
33
|
+
|| canonical(impl.realpathSync(parent)) !== canonical(parent)) throw new Error('Invalid private staging scope');
|
|
34
|
+
let stat;
|
|
35
|
+
try { stat = impl.lstatSync(staging); } catch (error) { if (error.code === 'ENOENT') return null; throw error; }
|
|
36
|
+
if (!stat.isDirectory() || stat.isSymbolicLink()
|
|
37
|
+
|| canonical(impl.realpathSync(staging)) !== canonical(staging)
|
|
38
|
+
|| (expected && (stat.dev !== expected.dev || stat.ino !== expected.ino))) throw new Error('Private staging identity changed');
|
|
39
|
+
return stat;
|
|
40
|
+
}
|
|
41
|
+
function renamePrivateStaging(source, target, options = {}) {
|
|
42
|
+
const impl = options.fsImpl || fs;
|
|
43
|
+
if ((options.platform || process.platform) !== 'win32') return impl.renameSync(source, target);
|
|
44
|
+
if (canonical(path.dirname(target)) !== canonical(path.dirname(source))
|
|
45
|
+
|| !/^(?:telegram|agent-spine-[a-f0-9]{64})$/.test(path.basename(target))) throw new Error('Invalid private staging target');
|
|
46
|
+
const identity = stagingState(source, impl);
|
|
47
|
+
if (!identity) throw new Error('Private staging is missing');
|
|
48
|
+
return withWindowsFsRetry(() => impl.renameSync(source, target), {
|
|
49
|
+
...options, label: 'Privates Plugin umbenennen', validate(original) {
|
|
50
|
+
if (!stagingState(source, impl, identity)) throw original || new Error('Private staging disappeared');
|
|
51
|
+
try { impl.lstatSync(target); } catch (error) { if (error.code === 'ENOENT') return; throw error; }
|
|
52
|
+
throw original || new Error('Private staging target already exists');
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
function copyTelegramFiles(source, staging, options = {}) {
|
|
57
|
+
const impl = options.fsImpl || fs;
|
|
58
|
+
if ((options.platform || process.platform) !== 'win32') return impl.cpSync(source, staging, { recursive: true, force: true });
|
|
59
|
+
if (!path.basename(staging).startsWith('.telegram-')) throw new Error('Invalid Telegram staging');
|
|
60
|
+
if (stagingState(staging, impl)) throw new Error('Telegram staging already exists');
|
|
61
|
+
let identity;
|
|
62
|
+
return withWindowsFsRetry(() => {
|
|
63
|
+
try { return impl.cpSync(source, staging, { recursive: true, force: true }); }
|
|
64
|
+
catch (error) { identity ||= stagingState(staging, impl); throw error; }
|
|
65
|
+
}, { ...options, label: 'Telegram-Dateien kopieren', validate() { stagingState(staging, impl, identity); } });
|
|
66
|
+
}
|
|
67
|
+
function writeTelegramTemplate(file, content, options = {}) {
|
|
68
|
+
const impl = options.fsImpl || fs;
|
|
69
|
+
const write = options.writeFile || require('./private-paths').writePrivateFile;
|
|
70
|
+
if ((options.platform || process.platform) !== 'win32') return write(file, content);
|
|
71
|
+
const parent = path.dirname(path.resolve(file));
|
|
72
|
+
if (path.basename(file) !== '.env' || path.basename(parent) !== 'telegram'
|
|
73
|
+
|| path.basename(path.dirname(parent)) !== 'channels'
|
|
74
|
+
|| canonical(impl.realpathSync(parent)) !== canonical(parent)) throw new Error('Invalid Telegram template scope');
|
|
75
|
+
return withWindowsFsRetry(() => write(file, content), {
|
|
76
|
+
...options, label: 'Telegram-Vorlagendatei schreiben', validate(original) {
|
|
77
|
+
try { impl.lstatSync(file); } catch (error) { if (error.code === 'ENOENT') return; throw error; }
|
|
78
|
+
throw original || new Error('Telegram template already exists; refusing overwrite');
|
|
79
|
+
},
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
module.exports = { withWindowsFsRetry, renamePrivateStaging, copyTelegramFiles, writeTelegramTemplate };
|
package/blun.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// BLUN_BUILD_INPUT_SHA256:
|
|
2
|
+
// BLUN_BUILD_INPUT_SHA256:c6e4e4587ddf016fdb40a775622da5455beee2ed97708e374bf683b38e4a9dea
|
|
3
3
|
/*! wasm-webp 0.1.0
|
|
4
4
|
MIT License
|
|
5
5
|
|
|
@@ -368248,7 +368248,7 @@ function optionalBuildString(value) {
|
|
|
368248
368248
|
var BLUN_BUILD_INFO;
|
|
368249
368249
|
var init_build_info = __esmMin((() => {
|
|
368250
368250
|
BLUN_BUILD_INFO = {
|
|
368251
|
-
version: optionalBuildString("9.1.628-pruefpaket.modules.
|
|
368251
|
+
version: optionalBuildString("9.1.628-pruefpaket.modules.r8"),
|
|
368252
368252
|
channel: typeof __BLUN_CHANNEL__ === "string" ? optionalBuildString(__BLUN_CHANNEL__) : void 0,
|
|
368253
368253
|
commit: typeof __BLUN_COMMIT__ === "string" ? optionalBuildString(__BLUN_COMMIT__) : void 0,
|
|
368254
368254
|
buildTarget: typeof __BLUN_BUILD_TARGET__ === "string" ? optionalBuildString(__BLUN_BUILD_TARGET__) : void 0
|
package/package.json
CHANGED
package/worker-host.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// BLUN_BUILD_INPUT_SHA256:
|
|
2
|
+
// BLUN_BUILD_INPUT_SHA256:c6e4e4587ddf016fdb40a775622da5455beee2ed97708e374bf683b38e4a9dea
|
|
3
3
|
/*! wasm-webp 0.1.0
|
|
4
4
|
MIT License
|
|
5
5
|
|
|
@@ -350041,7 +350041,7 @@ function optionalBuildString(value) {
|
|
|
350041
350041
|
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
350042
350042
|
}
|
|
350043
350043
|
const BLUN_BUILD_INFO = {
|
|
350044
|
-
version: optionalBuildString("9.1.628-pruefpaket.modules.
|
|
350044
|
+
version: optionalBuildString("9.1.628-pruefpaket.modules.r8"),
|
|
350045
350045
|
channel: typeof __BLUN_CHANNEL__ === "string" ? optionalBuildString(__BLUN_CHANNEL__) : void 0,
|
|
350046
350046
|
commit: typeof __BLUN_COMMIT__ === "string" ? optionalBuildString(__BLUN_COMMIT__) : void 0,
|
|
350047
350047
|
buildTarget: typeof __BLUN_BUILD_TARGET__ === "string" ? optionalBuildString(__BLUN_BUILD_TARGET__) : void 0
|