blun-king-cli 9.0.0 → 9.0.1
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 +1 -7
- package/README.md +4 -16
- package/bin/blun.js +248 -160
- package/bin/core-bootstrap.js +47 -0
- package/bin/king.js +277 -1
- package/bin/launcher-mode.js +2 -1
- package/bin/launcher-runtime.js +221 -0
- package/bin/plugin-bootstrap.js +0 -0
- package/bin/private-paths.js +0 -0
- package/bin/update-lease.js +399 -0
- package/bin/update-notice.js +1094 -0
- package/blun.mjs +4060 -6667
- package/package.json +3 -10
- package/skills/screenshot-lesen/SKILL.md +0 -1
- package/skills/web-lesen/SKILL.md +0 -1
- package/telegram-plugin/dist/bridge.mjs +1 -21
- package/mnemo/access_routes.js +0 -692
- package/mnemo/agent_governance.js +0 -4242
- package/mnemo/agent_mail.js +0 -901
- package/mnemo/bootstrap_auto.js +0 -137
- package/mnemo/brief_coordination.js +0 -226
- package/mnemo/code_read_tools.js +0 -375
- package/mnemo/context_preview_tools.js +0 -603
- package/mnemo/embeddings.js +0 -66
- package/mnemo/external_repo_ops.js +0 -575
- package/mnemo/facts/example-project-rules.json +0 -90
- package/mnemo/facts/example.json +0 -34
- package/mnemo/identity_schema.sql +0 -139
- package/mnemo/journal_schema.js +0 -561
- package/mnemo/loop_doctor_tools.js +0 -661
- package/mnemo/mail_secret_refs.js +0 -150
- package/mnemo/mcp.js +0 -9309
- package/mnemo/memory_consolidation.js +0 -1914
- package/mnemo/memory_health_tools.js +0 -165
- package/mnemo/package.json +0 -79
- package/mnemo/protected_scope_gate.js +0 -627
- package/mnemo/resource_access_control.js +0 -684
- package/mnemo/runtime_governance.js +0 -1256
- package/mnemo/runtime_turn_gate.js +0 -862
- package/mnemo/sandbox.js +0 -143
- package/mnemo/schema.sql +0 -389
- package/mnemo/shared_utils.js +0 -763
- package/mnemo/skills/agent-auto-resume/SKILL.md +0 -56
- package/mnemo/skills/agent_hand/SKILL.md +0 -43
- package/mnemo/skills/agent_hand/run.js +0 -63
- package/mnemo/skills/book_flight/SKILL.md +0 -34
- package/mnemo/skills/external_repo_review/SKILL.md +0 -43
- package/mnemo/skills/external_repo_review/run.js +0 -73
- package/mnemo/skills/pay_invoice/SKILL.md +0 -34
- package/mnemo/team_quality_ops.js +0 -944
- package/mnemo/timeline_report_tools.js +0 -810
- package/mnemo/write_gate_risk.js +0 -80
- package/mnemo/writer_health.js +0 -152
- package/skills/doku-ingestion/SKILL.md +0 -48
- package/skills/doku-ingestion/ingest_docs.py +0 -133
package/LIESMICH.txt
CHANGED
|
@@ -18,7 +18,7 @@ Start
|
|
|
18
18
|
king Dieselbe Konsole mit Telegram
|
|
19
19
|
|
|
20
20
|
Beim ersten Start in der Konsole /login eingeben. Der Browser öffnet den
|
|
21
|
-
BLUN-OAuth-Login.
|
|
21
|
+
BLUN-OAuth-Login.
|
|
22
22
|
Ein API-Schlüssel wird weder abgefragt noch in die Konfiguration geschrieben.
|
|
23
23
|
|
|
24
24
|
Telegram einrichten
|
|
@@ -30,9 +30,3 @@ Telegram einrichten
|
|
|
30
30
|
|
|
31
31
|
3. `king` starten und den angezeigten Pairing-Code mit
|
|
32
32
|
/telegram:access pair CODE bestätigen.
|
|
33
|
-
|
|
34
|
-
Lokale Daten
|
|
35
|
-
------------
|
|
36
|
-
Konfiguration, Anmeldedaten, Telegram-Zustand und Mnemo liegen unter ~/.blun.
|
|
37
|
-
Die privaten Verzeichnisse und Dateien werden auf macOS mit restriktiven
|
|
38
|
-
Dateirechten angelegt.
|
package/README.md
CHANGED
|
@@ -9,26 +9,14 @@ Der Schritt baut nichts, installiert nichts und veröffentlicht nichts. Vorher m
|
|
|
9
9
|
`apps/blun-king/dist/main.mjs`, `dist-web`, die Darwin-Natives sowie
|
|
10
10
|
`plugins/telegram/dist` bereits frisch gebaut sein.
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
werden; ein benutzerspezifischer Pfad wird nie angenommen:
|
|
14
|
-
|
|
15
|
-
```text
|
|
16
|
-
npm run stage:mac -- /pfad/zu/mnemo/packages/core
|
|
17
|
-
npm run pack:mac -- /pfad/zu/mnemo/packages/core
|
|
18
|
-
```
|
|
19
|
-
|
|
20
|
-
Alternativ kann `BLUN_MNEMO_SOURCE` gesetzt werden. Das Staging landet standardmäßig
|
|
12
|
+
Das Staging landet standardmäßig
|
|
21
13
|
unter `.stage/package`, das Tarball unter `.stage/artifacts`.
|
|
22
14
|
`BLUN_NPM_STAGE_DIR` und `BLUN_NPM_ARTIFACT_DIR` können beide Ziele überschreiben;
|
|
23
15
|
beim direkten Skriptaufruf stehen zusätzlich `--output` und `--artifacts` bereit.
|
|
24
16
|
|
|
25
17
|
Der Schritt validiert alle Pflichtartefakte vor dem Leeren des alten Stagings. Er
|
|
26
18
|
kopiert nur die Paket-Hülle, `blun.mjs`, `dist-web`, `native`, Telegrams
|
|
27
|
-
`dist`/Manifest/Commands
|
|
28
|
-
Mnemo-Zustände und Geheimnisse (`mnemo.db*`, `.env*`, `facts`, `logs`, `media`,
|
|
29
|
-
`sources`, `tenants`, `tmp`, `node_modules`) werden ausgeschlossen. Die
|
|
30
|
-
Mnemo-Abhängigkeiten werden in das gestagte `package.json` übernommen; Konflikte
|
|
31
|
-
brechen vor dem Packen ab.
|
|
19
|
+
`dist`/Manifest/Commands und die Repo-Skills.
|
|
32
20
|
|
|
33
21
|
## Startmodi
|
|
34
22
|
|
|
@@ -37,7 +25,7 @@ Konsole mit Telegram-Kanal, Pairing-Befehl und Telegram-Werkzeugen. Der
|
|
|
37
25
|
Unterschied gilt nur für den laufenden Prozess; die gespeicherte
|
|
38
26
|
Plugin-Konfiguration wird nicht umgeschrieben.
|
|
39
27
|
|
|
40
|
-
Beim ersten Start werden das Telegram-Plugin
|
|
41
|
-
|
|
28
|
+
Beim ersten Start werden das Telegram-Plugin und die mitgelieferten Skills
|
|
29
|
+
eingerichtet. Die Anmeldung erfolgt anschließend in der
|
|
42
30
|
Konsole mit `/login` über den BLUN-OAuth-Server. Das Paket erzeugt keine
|
|
43
31
|
statische Anbieter- oder API-Key-Konfiguration.
|
package/bin/blun.js
CHANGED
|
@@ -1,192 +1,280 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
'use strict';
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
* alles privat auf dem Rechner, ueberlebt Sessions und Updates.
|
|
8
|
-
* Danach: TUI starten. Anmeldung und Modellkatalog kommen ueber BLUN OAuth.
|
|
9
|
-
*/
|
|
3
|
+
|
|
4
|
+
// This public entry must stay self-contained. Package-local modules are loaded
|
|
5
|
+
// only after the update owner proves that the package root is not being replaced.
|
|
6
|
+
const { createHash } = require('node:crypto');
|
|
10
7
|
const fs = require('node:fs');
|
|
11
|
-
const
|
|
8
|
+
const net = require('node:net');
|
|
12
9
|
const os = require('node:os');
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
10
|
+
const path = require('node:path');
|
|
11
|
+
|
|
12
|
+
const LEASE_PROTOCOL_VERSION = 'BLUN_UPDATE_LEASE_V3';
|
|
13
|
+
const LEASE_PROBE_TIMEOUT_MS = 500;
|
|
14
|
+
const MAX_POSIX_SOCKET_PATH_BYTES = 96;
|
|
15
|
+
const PACKAGE_ROOT = path.resolve(__dirname, '..');
|
|
16
|
+
|
|
17
|
+
function deriveLeaseEndpoint(role) {
|
|
18
|
+
const realpath = fs.realpathSync.native || fs.realpathSync;
|
|
19
|
+
let identity = realpath(PACKAGE_ROOT);
|
|
20
|
+
if (!fs.statSync(identity).isDirectory()) throw new Error('INVALID_PACKAGE_ROOT');
|
|
21
|
+
identity = identity.replaceAll('\\', '/');
|
|
22
|
+
if (process.platform === 'win32') identity = identity.toLowerCase();
|
|
23
|
+
const digest = createHash('sha256')
|
|
24
|
+
.update(`blun-update-v3\0${role}\0${identity}`)
|
|
25
|
+
.digest('hex');
|
|
26
|
+
const magic = `${LEASE_PROTOCOL_VERSION} ${digest}\n`;
|
|
27
|
+
if (process.platform === 'win32') {
|
|
28
|
+
return {
|
|
29
|
+
magic,
|
|
30
|
+
path: `\\\\.\\pipe\\blun-update-v3-${digest.slice(0, 32)}`,
|
|
31
|
+
platform: 'win32',
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const uid = typeof process.getuid === 'function' ? process.getuid() : undefined;
|
|
36
|
+
const directoryName = `blun-update-${uid ?? digest.slice(0, 12)}`;
|
|
37
|
+
let directory = path.posix.join(os.tmpdir().replaceAll('\\', '/'), directoryName);
|
|
38
|
+
let socketPath = path.posix.join(directory, `lease-${digest.slice(0, 24)}.sock`);
|
|
39
|
+
if (Buffer.byteLength(socketPath, 'utf8') > MAX_POSIX_SOCKET_PATH_BYTES) {
|
|
40
|
+
directory = path.posix.join('/tmp', directoryName);
|
|
41
|
+
socketPath = path.posix.join(directory, `lease-${digest.slice(0, 24)}.sock`);
|
|
39
42
|
}
|
|
40
|
-
|
|
41
|
-
|
|
43
|
+
if (Buffer.byteLength(socketPath, 'utf8') > MAX_POSIX_SOCKET_PATH_BYTES) {
|
|
44
|
+
throw new Error('UPDATE_LEASE_SOCKET_PATH_TOO_LONG');
|
|
45
|
+
}
|
|
46
|
+
return { directory, magic, path: socketPath, platform: process.platform };
|
|
42
47
|
}
|
|
43
48
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
49
|
+
function ensurePrivateSocketDirectory(endpoint) {
|
|
50
|
+
if (endpoint.platform === 'win32') return;
|
|
51
|
+
fs.mkdirSync(endpoint.directory, { recursive: true, mode: 0o700 });
|
|
52
|
+
const stat = fs.lstatSync(endpoint.directory);
|
|
53
|
+
if (!stat.isDirectory() || stat.isSymbolicLink()) {
|
|
54
|
+
throw new Error('INVALID_UPDATE_LEASE_DIRECTORY');
|
|
55
|
+
}
|
|
56
|
+
if (typeof process.getuid === 'function'
|
|
57
|
+
&& Number.isInteger(stat.uid)
|
|
58
|
+
&& stat.uid !== process.getuid()) {
|
|
59
|
+
throw new Error('INVALID_UPDATE_LEASE_DIRECTORY_OWNER');
|
|
60
|
+
}
|
|
61
|
+
fs.chmodSync(endpoint.directory, 0o700);
|
|
47
62
|
}
|
|
48
63
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
res.on('data', (c) => (body += c));
|
|
69
|
-
res.on('end', () => {
|
|
70
|
-
try { resolve(JSON.parse(body).version); } catch { resolve(null); }
|
|
71
|
-
});
|
|
72
|
-
},
|
|
73
|
-
);
|
|
74
|
-
req.on('error', () => resolve(null));
|
|
75
|
-
req.on('timeout', () => { req.destroy(); resolve(null); });
|
|
64
|
+
function closeServer(server) {
|
|
65
|
+
return new Promise((resolve) => {
|
|
66
|
+
if (!server.listening) {
|
|
67
|
+
resolve();
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
try {
|
|
71
|
+
server.close(() => resolve());
|
|
72
|
+
} catch {
|
|
73
|
+
resolve();
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function listenForLease(endpoint) {
|
|
79
|
+
return new Promise((resolve, reject) => {
|
|
80
|
+
const server = net.createServer((socket) => {
|
|
81
|
+
socket.on('error', () => {});
|
|
82
|
+
socket.end(endpoint.magic);
|
|
76
83
|
});
|
|
84
|
+
const onError = (error) => {
|
|
85
|
+
server.removeListener('error', onError);
|
|
86
|
+
reject(error);
|
|
87
|
+
};
|
|
88
|
+
server.once('error', onError);
|
|
89
|
+
server.listen({ path: endpoint.path, exclusive: true }, () => {
|
|
90
|
+
server.removeListener('error', onError);
|
|
91
|
+
resolve(server);
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
}
|
|
77
95
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
96
|
+
function probeLease(endpoint) {
|
|
97
|
+
return new Promise((resolve) => {
|
|
98
|
+
let received = '';
|
|
99
|
+
let settled = false;
|
|
100
|
+
let socket;
|
|
101
|
+
const finish = (state) => {
|
|
102
|
+
if (settled) return;
|
|
103
|
+
settled = true;
|
|
104
|
+
clearTimeout(timer);
|
|
105
|
+
try {
|
|
106
|
+
socket?.destroy();
|
|
107
|
+
} catch {}
|
|
108
|
+
// oxlint-disable-next-line promise/no-multiple-resolved -- settled is the idempotence guard.
|
|
109
|
+
resolve(state);
|
|
110
|
+
};
|
|
111
|
+
const timer = setTimeout(() => finish('foreign'), LEASE_PROBE_TIMEOUT_MS);
|
|
112
|
+
timer.unref?.();
|
|
113
|
+
try {
|
|
114
|
+
socket = net.createConnection({ path: endpoint.path });
|
|
115
|
+
socket.setEncoding('utf8');
|
|
116
|
+
socket.on('data', (chunk) => {
|
|
117
|
+
received += String(chunk);
|
|
118
|
+
if (Buffer.byteLength(received, 'utf8') > Buffer.byteLength(endpoint.magic, 'utf8')) {
|
|
119
|
+
finish('foreign');
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
socket.once('end', () => finish(received === endpoint.magic ? 'owner' : 'foreign'));
|
|
123
|
+
socket.once('error', (error) => {
|
|
124
|
+
finish(error?.code === 'ECONNREFUSED' || error?.code === 'ENOENT'
|
|
125
|
+
? 'stale'
|
|
126
|
+
: 'foreign');
|
|
127
|
+
});
|
|
128
|
+
socket.once('close', () => {
|
|
129
|
+
if (!settled) finish(received === endpoint.magic ? 'owner' : 'foreign');
|
|
130
|
+
});
|
|
131
|
+
} catch (error) {
|
|
132
|
+
finish(error?.code === 'ECONNREFUSED' || error?.code === 'ENOENT'
|
|
133
|
+
? 'stale'
|
|
134
|
+
: 'foreign');
|
|
84
135
|
}
|
|
85
|
-
}
|
|
86
|
-
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function observeSocket(endpoint) {
|
|
140
|
+
if (endpoint.platform === 'win32') return { state: 'not_applicable' };
|
|
141
|
+
try {
|
|
142
|
+
const stat = fs.lstatSync(endpoint.path);
|
|
143
|
+
if (!stat.isSocket() || stat.isSymbolicLink()) return { state: 'unsafe' };
|
|
144
|
+
return { dev: stat.dev, ino: stat.ino, state: 'socket' };
|
|
145
|
+
} catch (error) {
|
|
146
|
+
return error?.code === 'ENOENT' ? { state: 'missing' } : { state: 'unsafe' };
|
|
87
147
|
}
|
|
88
148
|
}
|
|
89
149
|
|
|
90
|
-
function
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
if (
|
|
95
|
-
|
|
150
|
+
function removeObservedSocket(endpoint, observed) {
|
|
151
|
+
if (endpoint.platform === 'win32') return false;
|
|
152
|
+
try {
|
|
153
|
+
const stat = fs.lstatSync(endpoint.path);
|
|
154
|
+
if (observed.state !== 'socket'
|
|
155
|
+
|| !stat.isSocket()
|
|
156
|
+
|| stat.isSymbolicLink()
|
|
157
|
+
|| stat.dev !== observed.dev
|
|
158
|
+
|| stat.ino !== observed.ino) {
|
|
159
|
+
return false;
|
|
160
|
+
}
|
|
161
|
+
fs.unlinkSync(endpoint.path);
|
|
162
|
+
return true;
|
|
163
|
+
} catch (error) {
|
|
164
|
+
return error?.code === 'ENOENT' && observed.state === 'missing';
|
|
96
165
|
}
|
|
97
|
-
return false;
|
|
98
166
|
}
|
|
99
167
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
} else {
|
|
118
|
-
securePrivateFile(envFile);
|
|
168
|
+
function recordSocket(endpoint) {
|
|
169
|
+
if (endpoint.platform === 'win32') return undefined;
|
|
170
|
+
const stat = fs.lstatSync(endpoint.path);
|
|
171
|
+
if (!stat.isSocket() || stat.isSymbolicLink()) throw new Error('INVALID_UPDATE_LEASE_SOCKET');
|
|
172
|
+
fs.chmodSync(endpoint.path, 0o600);
|
|
173
|
+
return { dev: stat.dev, ino: stat.ino };
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function removeOwnedSocket(endpoint, identity) {
|
|
177
|
+
if (endpoint.platform === 'win32' || !identity) return;
|
|
178
|
+
try {
|
|
179
|
+
const stat = fs.lstatSync(endpoint.path);
|
|
180
|
+
if (stat.isSocket()
|
|
181
|
+
&& !stat.isSymbolicLink()
|
|
182
|
+
&& stat.dev === identity.dev
|
|
183
|
+
&& stat.ino === identity.ino) {
|
|
184
|
+
fs.unlinkSync(endpoint.path);
|
|
119
185
|
}
|
|
186
|
+
} catch {}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
async function acquireNoticeLease() {
|
|
190
|
+
let endpoint;
|
|
191
|
+
try {
|
|
192
|
+
endpoint = deriveLeaseEndpoint('notice');
|
|
193
|
+
ensurePrivateSocketDirectory(endpoint);
|
|
194
|
+
} catch {
|
|
195
|
+
return { acquired: false, reason: 'unavailable' };
|
|
120
196
|
}
|
|
121
197
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
const
|
|
130
|
-
|
|
131
|
-
|
|
198
|
+
let server;
|
|
199
|
+
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
200
|
+
try {
|
|
201
|
+
server = await listenForLease(endpoint);
|
|
202
|
+
break;
|
|
203
|
+
} catch (error) {
|
|
204
|
+
if (error?.code !== 'EADDRINUSE') return { acquired: false, reason: 'unavailable' };
|
|
205
|
+
const observed = observeSocket(endpoint);
|
|
206
|
+
const state = await probeLease(endpoint);
|
|
207
|
+
if (state === 'owner') return { acquired: false, reason: 'busy' };
|
|
208
|
+
if (state !== 'stale' || attempt > 0) return { acquired: false, reason: 'unavailable' };
|
|
209
|
+
if (endpoint.platform === 'win32') continue;
|
|
210
|
+
if (!removeObservedSocket(endpoint, observed)) {
|
|
211
|
+
return { acquired: false, reason: 'unavailable' };
|
|
132
212
|
}
|
|
133
213
|
}
|
|
134
214
|
}
|
|
215
|
+
if (!server) return { acquired: false, reason: 'unavailable' };
|
|
216
|
+
|
|
217
|
+
let identity;
|
|
218
|
+
try {
|
|
219
|
+
identity = recordSocket(endpoint);
|
|
220
|
+
} catch {
|
|
221
|
+
await closeServer(server);
|
|
222
|
+
return { acquired: false, reason: 'unavailable' };
|
|
223
|
+
}
|
|
135
224
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
.
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
env: {
|
|
146
|
-
...process.env,
|
|
147
|
-
BLUN_HOME: BLUN,
|
|
148
|
-
MNEMO_DB: dbFile,
|
|
149
|
-
MNEMO_AGENT: agent,
|
|
150
|
-
MNEMO_OWNER_NAME: agent,
|
|
151
|
-
MNEMO_FACTS_DIR: factsDir,
|
|
152
|
-
MNEMO_ENV_FILE: path.join(memHome, '.env.local'),
|
|
153
|
-
MNEMO_HOOK_ENV_FILE: path.join(BLUN, '.mnemo-hook.env'),
|
|
154
|
-
MNEMO_HUB_URL: '',
|
|
225
|
+
let owned = true;
|
|
226
|
+
let released = false;
|
|
227
|
+
server.on('error', () => { owned = false; });
|
|
228
|
+
server.on('close', () => { owned = false; });
|
|
229
|
+
return {
|
|
230
|
+
acquired: true,
|
|
231
|
+
lease: Object.freeze({
|
|
232
|
+
assertOwned() {
|
|
233
|
+
if (!owned || !server.listening) throw new Error('UPDATE_LEASE_LOST');
|
|
155
234
|
},
|
|
156
|
-
|
|
235
|
+
async release() {
|
|
236
|
+
if (released) return;
|
|
237
|
+
released = true;
|
|
238
|
+
owned = false;
|
|
239
|
+
await closeServer(server);
|
|
240
|
+
removeOwnedSocket(endpoint, identity);
|
|
241
|
+
},
|
|
242
|
+
}),
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
async function installLeaseIsClear() {
|
|
247
|
+
try {
|
|
248
|
+
const endpoint = deriveLeaseEndpoint('install');
|
|
249
|
+
ensurePrivateSocketDirectory(endpoint);
|
|
250
|
+
return await probeLease(endpoint) === 'stale';
|
|
251
|
+
} catch {
|
|
252
|
+
return false;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
async function main() {
|
|
257
|
+
const result = await acquireNoticeLease();
|
|
258
|
+
if (!result.acquired) return;
|
|
259
|
+
try {
|
|
260
|
+
if (!await installLeaseIsClear()) return;
|
|
261
|
+
result.lease.assertOwned();
|
|
262
|
+
const { runLauncher } = require('./launcher-runtime');
|
|
263
|
+
result.lease.assertOwned();
|
|
264
|
+
const filename = path.basename(__filename).toLowerCase();
|
|
265
|
+
await runLauncher({
|
|
266
|
+
mode: filename.startsWith('king') ? 'king' : 'blun',
|
|
267
|
+
noticeLease: result.lease,
|
|
157
268
|
});
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
} else {
|
|
161
|
-
console.error(' Warnung: Memory konnte nicht angelegt werden — BLUN laeuft trotzdem, nur ohne Langzeitgedaechtnis.');
|
|
162
|
-
}
|
|
269
|
+
} finally {
|
|
270
|
+
await result.lease.release();
|
|
163
271
|
}
|
|
272
|
+
}
|
|
164
273
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
// --- 4. Start -------------------------------------------------------------
|
|
170
|
-
const env = createLauncherEnvironment(process.env, MODE);
|
|
171
|
-
env.BLUN_HOME = BLUN;
|
|
172
|
-
env.BLUN_MODEL_MAX_COMPLETION_TOKENS = env.BLUN_MODEL_MAX_COMPLETION_TOKENS || '32768';
|
|
173
|
-
env.BLUN_MNEMO_MCP = path.join(PKG, 'mnemo', 'mcp.js');
|
|
174
|
-
env.BLUN_MNEMO_AGENT = agent;
|
|
175
|
-
env.MNEMO_DB = dbFile;
|
|
176
|
-
env.MNEMO_FACTS_DIR = factsDir;
|
|
177
|
-
env.MNEMO_HUB_URL = '';
|
|
178
|
-
|
|
179
|
-
const workspace = path.join(HOME, 'BLUN-Workspace');
|
|
180
|
-
fs.mkdirSync(workspace, { recursive: true });
|
|
181
|
-
|
|
182
|
-
const child = spawn(process.execPath, [path.join(PKG, 'blun.mjs'), '--yolo', ...ARGS], {
|
|
183
|
-
cwd: workspace,
|
|
184
|
-
env,
|
|
185
|
-
stdio: 'inherit',
|
|
186
|
-
});
|
|
187
|
-
child.on('exit', (code) => process.exit(code ?? 0));
|
|
188
|
-
})().catch((error) => {
|
|
274
|
+
// oxlint-disable-next-line unicorn/prefer-top-level-await -- Public npm bins are CommonJS.
|
|
275
|
+
void main().catch((error) => {
|
|
276
|
+
// oxlint-disable-next-line no-console -- Fatal launcher errors belong on stderr.
|
|
189
277
|
console.error('BLUN Start fehlgeschlagen: '
|
|
190
278
|
+ (error && error.message ? error.message : String(error)));
|
|
191
|
-
process.
|
|
279
|
+
process.exitCode = 1;
|
|
192
280
|
});
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const path = require('node:path');
|
|
4
|
+
const { pathToFileURL } = require('node:url');
|
|
5
|
+
|
|
6
|
+
const { tryAcquireUpdateLease } = require('./update-lease');
|
|
7
|
+
|
|
8
|
+
const CORE_LOADED_MESSAGE = 'blun-core-bootstrap-loaded';
|
|
9
|
+
|
|
10
|
+
async function runCoreBootstrap() {
|
|
11
|
+
const packageRoot = path.resolve(__dirname, '..');
|
|
12
|
+
const mainPath = path.join(packageRoot, 'blun.mjs');
|
|
13
|
+
const leaseResult = await tryAcquireUpdateLease({ packageRoot, role: 'install' });
|
|
14
|
+
if (!leaseResult.acquired) process.exit(75);
|
|
15
|
+
|
|
16
|
+
try {
|
|
17
|
+
leaseResult.lease.assertOwned();
|
|
18
|
+
process.argv = [process.execPath, mainPath, ...process.argv.slice(2)];
|
|
19
|
+
await import(pathToFileURL(mainPath).href);
|
|
20
|
+
leaseResult.lease.assertOwned();
|
|
21
|
+
} catch (error) {
|
|
22
|
+
await leaseResult.lease.release();
|
|
23
|
+
throw error;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// The bundled CLI loads package assets lazily after module evaluation. Keep
|
|
27
|
+
// the install lease for this process's lifetime, without keeping print/help
|
|
28
|
+
// commands alive solely because of the owner server.
|
|
29
|
+
leaseResult.lease.unref();
|
|
30
|
+
|
|
31
|
+
if (process.connected) {
|
|
32
|
+
try {
|
|
33
|
+
process.send({ type: CORE_LOADED_MESSAGE }, () => {
|
|
34
|
+
try {
|
|
35
|
+
process.disconnect();
|
|
36
|
+
} catch {}
|
|
37
|
+
});
|
|
38
|
+
} catch {}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (require.main === module) {
|
|
43
|
+
// oxlint-disable-next-line unicorn/prefer-top-level-await -- Worker entry is CommonJS.
|
|
44
|
+
void runCoreBootstrap().catch(() => process.exit(1));
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
module.exports = { CORE_LOADED_MESSAGE, runCoreBootstrap };
|