blun-king-cli 9.1.52 → 9.1.57
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 +39 -4
- package/README.md +39 -4
- package/bin/plugin-bootstrap.js +95 -15
- package/bin/private-paths.js +30 -10
- package/blun.mjs +212 -22
- package/package.json +1 -1
package/LIESMICH.txt
CHANGED
|
@@ -9,7 +9,7 @@ Installation
|
|
|
9
9
|
------------
|
|
10
10
|
Die geprüfte Version exakt global installieren:
|
|
11
11
|
|
|
12
|
-
npm install -g blun-king-cli@9.1.
|
|
12
|
+
npm install -g blun-king-cli@9.1.57
|
|
13
13
|
|
|
14
14
|
Start
|
|
15
15
|
-----
|
|
@@ -20,10 +20,30 @@ Start
|
|
|
20
20
|
Beide Startbefehle verwenden dieselbe Version, dasselbe Konto, dasselbe Modell
|
|
21
21
|
und dieselben Befehle.
|
|
22
22
|
|
|
23
|
+
Zuverlässiger King-Start
|
|
24
|
+
-----------------------
|
|
25
|
+
Der Windows-Hilfsprozess für private Pfade übernimmt TEMP und TMP aus der
|
|
26
|
+
Benutzersitzung. Dadurch arbeitet die ACL-Prüfung auch unter einem normalen
|
|
27
|
+
Benutzerkonto. Bei einem Fehler wird die tatsächliche Ursache des
|
|
28
|
+
Unterprozesses angezeigt.
|
|
29
|
+
|
|
30
|
+
Ein laufender Telegram-Prozess blockiert Aktualisierungen nicht mehr.
|
|
31
|
+
Unveränderte Plugin-Dateien werden wiederverwendet; geänderte Dateien werden in
|
|
32
|
+
einem inhaltsadressierten Verzeichnis daneben installiert. Der neue Prozess
|
|
33
|
+
verwendet den neuen Pfad, während der bisherige Prozess seinen geladenen Stand
|
|
34
|
+
geordnet beenden kann.
|
|
35
|
+
|
|
23
36
|
Beim Fortsetzen mit --continue oder --session wird der Arbeitsbereich der
|
|
24
37
|
bestehenden Sitzung direkt verwendet. Die Ordnerauswahl bleibt neuen Starts
|
|
25
38
|
vorbehalten und kann eine Fortsetzung daher nicht mehr vorzeitig beenden.
|
|
26
39
|
|
|
40
|
+
Schnellstart
|
|
41
|
+
------------
|
|
42
|
+
Beim Starten oder Fortsetzen einer Sitzung werden die Skill-Verzeichnisse
|
|
43
|
+
parallel geprüft und eingelesen. Die Skills werden weiterhin in der
|
|
44
|
+
ursprünglichen, festen Reihenfolge registriert; Priorität und Verhalten bei
|
|
45
|
+
doppelten Namen bleiben unverändert.
|
|
46
|
+
|
|
27
47
|
Zug-Wächter
|
|
28
48
|
-----------
|
|
29
49
|
Läuft ein Zug 20 Minuten ohne neues Werkzeugergebnis, meldet die Konsole den
|
|
@@ -59,8 +79,20 @@ lokale Schritte um und fragt gezielt nach, wenn eine Entscheidung oder ein Zugan
|
|
|
59
79
|
fehlt. Blockierte, auf Freigabe wartende und abgebrochene Schritte bleiben mit
|
|
60
80
|
ihrem tatsächlichen Zustand sichtbar.
|
|
61
81
|
|
|
82
|
+
Der erste Werkzeugaufruf muss den sichtbaren Plan setzen. Ausgehende Aktionen
|
|
83
|
+
werden zusätzlich pro Werkzeugaufruf geprüft und bei fehlender Kanal- oder
|
|
84
|
+
Zugangsfreigabe angehalten.
|
|
85
|
+
|
|
86
|
+
/chancenradar <Ziel> startet einen eigenen Modus zur Chancensuche. Der Agent
|
|
87
|
+
durchsucht aktuelle öffentliche Quellen, Foren und Communitys nach echten
|
|
88
|
+
Problemen und bestehenden Lösungsansätzen, trennt Belege von Schlussfolgerungen,
|
|
89
|
+
bewertet Chancen nach Wirkung, Passung, Aufwand und Verlässlichkeit und setzt den
|
|
90
|
+
besten sicheren lokalen nächsten Schritt um. Der sichtbare Aufgabenplan und alle
|
|
91
|
+
Freigabegrenzen von /idea gelten weiterhin. Der Befehl funktioniert auch über
|
|
92
|
+
Telegram.
|
|
93
|
+
|
|
62
94
|
Im automatisch angebundenen Telegram-Kanal werden ausschließlich /loop, /goal,
|
|
63
|
-
/idea und /befehle als Befehle ausgeführt. Ist der Agent beschäftigt, bleiben
|
|
95
|
+
/idea, /chancenradar und /befehle als Befehle ausgeführt. Ist der Agent beschäftigt, bleiben
|
|
64
96
|
sie in der Warteschlange. Andere Slash-Eingaben werden aus Sicherheitsgründen
|
|
65
97
|
als normale Chatnachrichten behandelt.
|
|
66
98
|
|
|
@@ -87,8 +119,11 @@ anschließend mit GetMedia abgerufen.
|
|
|
87
119
|
Die Medienleiste erscheint nur bei aktiven Medienaufträgen. Sie zeigt
|
|
88
120
|
Warteschlangenplatz, Produktionsphase und Laufzeit sowie nur tatsächlich vom
|
|
89
121
|
Anbieter gemeldete Prozentwerte, Schritte, Frames, FPS, Audiolänge, Restzeit,
|
|
90
|
-
Auflösung, Wellenform und Vorschauen.
|
|
91
|
-
|
|
122
|
+
Auflösung, Wellenform und Vorschauen. Bei Bild-zu-Video erscheint das lokale
|
|
123
|
+
Ausgangsbild sofort als Vorschau. Tatsächlich gemeldete Phasen bleiben erhalten,
|
|
124
|
+
sodass auch kurze Schritte wie Rendern und Speichern in der Produktionskette
|
|
125
|
+
sichtbar sind. Aktualisierte Vorschaubilder werden erneuert; Medienpfade und
|
|
126
|
+
Webadressen sind als Terminalverweise anklickbar.
|
|
92
127
|
|
|
93
128
|
Aktualisieren
|
|
94
129
|
-------------
|
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.
|
|
12
|
+
npm install -g blun-king-cli@9.1.57
|
|
13
13
|
```
|
|
14
14
|
|
|
15
15
|
## Reproduzierbares Staging und Packen
|
|
@@ -35,10 +35,30 @@ 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
|
+
## Zuverlässiger King-Start
|
|
39
|
+
|
|
40
|
+
Der Windows-Hilfsprozess für private Pfade übernimmt `TEMP` und `TMP` aus der
|
|
41
|
+
Benutzersitzung. Dadurch kann der C#-Compiler für die ACL-Prüfung auch unter
|
|
42
|
+
einem normalen Benutzerkonto arbeiten. Scheitert der Unterprozess, nennt die
|
|
43
|
+
Fehlermeldung jetzt dessen tatsächliche Ursache.
|
|
44
|
+
|
|
45
|
+
Ein bereits laufender Telegram-Prozess blockiert Aktualisierungen nicht mehr.
|
|
46
|
+
Unveränderte Plugin-Dateien werden wiederverwendet; geänderte Dateien werden in
|
|
47
|
+
einem inhaltsadressierten Verzeichnis daneben installiert. Der neue Prozess
|
|
48
|
+
verwendet den neuen Pfad, während der bisherige Prozess seinen geladenen Stand
|
|
49
|
+
geordnet beenden kann.
|
|
50
|
+
|
|
38
51
|
Beim Fortsetzen mit `--continue` oder `--session` wird der Arbeitsbereich der
|
|
39
52
|
bestehenden Sitzung direkt verwendet. Die Ordnerauswahl bleibt neuen Starts
|
|
40
53
|
vorbehalten und kann eine Fortsetzung daher nicht mehr vorzeitig beenden.
|
|
41
54
|
|
|
55
|
+
## Schnellstart
|
|
56
|
+
|
|
57
|
+
Beim Starten oder Fortsetzen einer Sitzung werden die Skill-Verzeichnisse
|
|
58
|
+
parallel geprüft und eingelesen. Die Skills werden weiterhin in der
|
|
59
|
+
ursprünglichen, festen Reihenfolge registriert; Priorität und Verhalten bei
|
|
60
|
+
doppelten Namen bleiben unverändert.
|
|
61
|
+
|
|
42
62
|
## Zug-Wächter
|
|
43
63
|
|
|
44
64
|
Läuft ein Zug 20 Minuten ohne neues Werkzeugergebnis, meldet die Konsole den
|
|
@@ -74,8 +94,20 @@ lokale Schritte um und fragt gezielt nach, wenn eine Entscheidung oder ein Zugan
|
|
|
74
94
|
fehlt. Blockierte, auf Freigabe wartende und abgebrochene Schritte bleiben mit
|
|
75
95
|
ihrem tatsächlichen Zustand sichtbar.
|
|
76
96
|
|
|
97
|
+
Der erste Werkzeugaufruf muss den sichtbaren Plan setzen. Ausgehende Aktionen
|
|
98
|
+
werden zusätzlich pro Werkzeugaufruf geprüft und bei fehlender Kanal- oder
|
|
99
|
+
Zugangsfreigabe angehalten.
|
|
100
|
+
|
|
101
|
+
`/chancenradar <Ziel>` startet einen eigenen Modus zur Chancensuche. Der Agent
|
|
102
|
+
durchsucht aktuelle öffentliche Quellen, Foren und Communitys nach echten
|
|
103
|
+
Problemen und bestehenden Lösungsansätzen, trennt Belege von Schlussfolgerungen,
|
|
104
|
+
bewertet Chancen nach Wirkung, Passung, Aufwand und Verlässlichkeit und setzt den
|
|
105
|
+
besten sicheren lokalen nächsten Schritt um. Der sichtbare Aufgabenplan und alle
|
|
106
|
+
Freigabegrenzen von `/idea` gelten weiterhin. Der Befehl funktioniert auch über
|
|
107
|
+
Telegram.
|
|
108
|
+
|
|
77
109
|
Im automatisch angebundenen Telegram-Kanal werden ausschließlich `/loop`,
|
|
78
|
-
`/goal`, `/idea` und `/befehle` als Befehle ausgeführt. Ist der Agent
|
|
110
|
+
`/goal`, `/idea`, `/chancenradar` und `/befehle` als Befehle ausgeführt. Ist der Agent
|
|
79
111
|
beschäftigt, bleiben sie in der Warteschlange. Andere Slash-Eingaben werden aus
|
|
80
112
|
Sicherheitsgründen als normale Chatnachrichten behandelt.
|
|
81
113
|
|
|
@@ -108,8 +140,11 @@ anschließend mit `GetMedia` abgerufen.
|
|
|
108
140
|
Die Medienleiste erscheint nur bei aktiven Medienaufträgen. Sie zeigt
|
|
109
141
|
Warteschlangenplatz, Produktionsphase und Laufzeit sowie nur tatsächlich vom
|
|
110
142
|
Anbieter gemeldete Prozentwerte, Schritte, Frames, FPS, Audiolänge, Restzeit,
|
|
111
|
-
Auflösung, Wellenform und Vorschauen.
|
|
112
|
-
|
|
143
|
+
Auflösung, Wellenform und Vorschauen. Bei Bild-zu-Video erscheint das lokale
|
|
144
|
+
Ausgangsbild sofort als Vorschau. Tatsächlich gemeldete Phasen bleiben erhalten,
|
|
145
|
+
sodass auch kurze Schritte wie Rendern und Speichern in der Produktionskette
|
|
146
|
+
sichtbar sind. Aktualisierte Vorschaubilder werden erneuert; Medienpfade und
|
|
147
|
+
Webadressen sind als Terminalverweise anklickbar.
|
|
113
148
|
|
|
114
149
|
## Aktualisieren
|
|
115
150
|
|
package/bin/plugin-bootstrap.js
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
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
|
|
|
7
7
|
const {
|
|
8
8
|
ensurePrivateDirectory,
|
|
9
9
|
securePrivateFile,
|
|
10
|
+
securePrivateTree,
|
|
10
11
|
writePrivateFile,
|
|
11
12
|
} = require('./private-paths');
|
|
12
13
|
|
|
@@ -14,6 +15,76 @@ function isObject(value) {
|
|
|
14
15
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
15
16
|
}
|
|
16
17
|
|
|
18
|
+
function snapshotPluginTree(root) {
|
|
19
|
+
const entries = [];
|
|
20
|
+
const visit = (directory, relativeDirectory = '') => {
|
|
21
|
+
for (const entry of fs.readdirSync(directory, { withFileTypes: true })
|
|
22
|
+
.sort((left, right) => left.name < right.name ? -1 : left.name > right.name ? 1 : 0)) {
|
|
23
|
+
const relativePath = path.join(relativeDirectory, entry.name);
|
|
24
|
+
const absolutePath = path.join(directory, entry.name);
|
|
25
|
+
const stat = fs.lstatSync(absolutePath);
|
|
26
|
+
if (stat.isSymbolicLink()) return false;
|
|
27
|
+
if (stat.isDirectory()) {
|
|
28
|
+
entries.push({ kind: 'directory', path: relativePath });
|
|
29
|
+
if (!visit(absolutePath, relativePath)) return false;
|
|
30
|
+
} else if (stat.isFile()) {
|
|
31
|
+
entries.push({
|
|
32
|
+
kind: 'file',
|
|
33
|
+
path: relativePath,
|
|
34
|
+
content: fs.readFileSync(absolutePath),
|
|
35
|
+
});
|
|
36
|
+
} else {
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return true;
|
|
41
|
+
};
|
|
42
|
+
return visit(root) ? entries : null;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function pluginTreesAreIdentical(sourceRoot, pluginRoot) {
|
|
46
|
+
try {
|
|
47
|
+
const source = snapshotPluginTree(sourceRoot);
|
|
48
|
+
const installed = snapshotPluginTree(pluginRoot);
|
|
49
|
+
if (source === null || installed === null || source.length !== installed.length) return false;
|
|
50
|
+
return source.every((entry, index) => {
|
|
51
|
+
const other = installed[index];
|
|
52
|
+
return entry.kind === other.kind
|
|
53
|
+
&& entry.path === other.path
|
|
54
|
+
&& (entry.kind !== 'file' || entry.content.equals(other.content));
|
|
55
|
+
});
|
|
56
|
+
} catch {
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function pluginTreeDigest(root) {
|
|
62
|
+
const entries = snapshotPluginTree(root);
|
|
63
|
+
if (entries === null) throw new Error(`Invalid managed plugin tree: ${root}`);
|
|
64
|
+
const hash = createHash('sha256');
|
|
65
|
+
for (const entry of entries) {
|
|
66
|
+
const metadata = Buffer.from(`${entry.kind}\0${entry.path}\0`, 'utf8');
|
|
67
|
+
hash.update(metadata);
|
|
68
|
+
if (entry.kind === 'file') {
|
|
69
|
+
const length = Buffer.allocUnsafe(8);
|
|
70
|
+
length.writeBigUInt64BE(BigInt(entry.content.length));
|
|
71
|
+
hash.update(length);
|
|
72
|
+
hash.update(entry.content);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return hash.digest('hex');
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function hasCurrentTelegramRecord(installed, pluginRoot) {
|
|
79
|
+
const records = installed.plugins.filter((plugin) => plugin.id === 'telegram');
|
|
80
|
+
if (records.length !== 1) return false;
|
|
81
|
+
const [telegram] = records;
|
|
82
|
+
return telegram.root === pluginRoot
|
|
83
|
+
&& telegram.source === 'local-path'
|
|
84
|
+
&& telegram.enabled === true
|
|
85
|
+
&& telegram.capabilities?.mcpServers?.telegram?.enabled === true;
|
|
86
|
+
}
|
|
87
|
+
|
|
17
88
|
function readInstalledPlugins(filePath) {
|
|
18
89
|
if (!fs.existsSync(filePath)) return { version: 1, plugins: [] };
|
|
19
90
|
|
|
@@ -82,43 +153,52 @@ function installManagedTelegramPlugin({ packageRoot, blunDir, now = new Date().t
|
|
|
82
153
|
const sourceRoot = path.join(packageRoot, 'telegram-plugin');
|
|
83
154
|
const pluginsDir = path.join(blunDir, 'plugins');
|
|
84
155
|
const managedDir = path.join(pluginsDir, 'managed');
|
|
85
|
-
const
|
|
156
|
+
const legacyPluginRoot = path.join(managedDir, 'telegram');
|
|
86
157
|
const installedFile = path.join(pluginsDir, 'installed.json');
|
|
87
158
|
|
|
88
159
|
ensurePrivateDirectory(pluginsDir);
|
|
89
160
|
if (fs.existsSync(installedFile)) securePrivateFile(installedFile);
|
|
90
161
|
const installed = readInstalledPlugins(installedFile);
|
|
91
|
-
const updatedInstalled = updatedInstalledPlugins(installed, pluginRoot, now);
|
|
92
162
|
ensurePrivateDirectory(managedDir);
|
|
163
|
+
if (fs.existsSync(legacyPluginRoot)
|
|
164
|
+
&& pluginTreesAreIdentical(sourceRoot, legacyPluginRoot)) {
|
|
165
|
+
if (!hasCurrentTelegramRecord(installed, legacyPluginRoot)) {
|
|
166
|
+
const updatedInstalled = updatedInstalledPlugins(installed, legacyPluginRoot, now);
|
|
167
|
+
writeInstalledPlugins(installedFile, updatedInstalled);
|
|
168
|
+
}
|
|
169
|
+
return { installedFile, pluginRoot: legacyPluginRoot, reused: true };
|
|
170
|
+
}
|
|
171
|
+
const sourceDigest = pluginTreeDigest(sourceRoot);
|
|
172
|
+
const pluginRoot = path.join(managedDir, `telegram-${sourceDigest}`);
|
|
173
|
+
const updatedInstalled = updatedInstalledPlugins(installed, pluginRoot, now);
|
|
174
|
+
if (fs.existsSync(pluginRoot)) {
|
|
175
|
+
if (!pluginTreesAreIdentical(sourceRoot, pluginRoot)) {
|
|
176
|
+
throw new Error(`Managed Telegram plugin digest mismatch: ${pluginRoot}`);
|
|
177
|
+
}
|
|
178
|
+
if (!hasCurrentTelegramRecord(installed, pluginRoot)) {
|
|
179
|
+
writeInstalledPlugins(installedFile, updatedInstalled);
|
|
180
|
+
}
|
|
181
|
+
return { installedFile, pluginRoot, reused: true };
|
|
182
|
+
}
|
|
93
183
|
const nonce = `${process.pid}-${randomUUID()}`;
|
|
94
184
|
const stagingRoot = path.join(managedDir, `.telegram-${nonce}.tmp`);
|
|
95
|
-
const backupRoot = path.join(managedDir, `.telegram-${nonce}.bak`);
|
|
96
|
-
let hadPreviousPlugin = false;
|
|
97
185
|
let installedNewPlugin = false;
|
|
98
186
|
try {
|
|
99
|
-
if (fs.existsSync(pluginRoot)) {
|
|
100
|
-
ensurePrivateDirectory(pluginRoot);
|
|
101
|
-
hadPreviousPlugin = true;
|
|
102
|
-
}
|
|
103
187
|
fs.cpSync(sourceRoot, stagingRoot, { recursive: true, force: true });
|
|
104
|
-
|
|
188
|
+
securePrivateTree(stagingRoot);
|
|
105
189
|
try {
|
|
106
190
|
fs.renameSync(stagingRoot, pluginRoot);
|
|
107
191
|
installedNewPlugin = true;
|
|
108
192
|
writeInstalledPlugins(installedFile, updatedInstalled);
|
|
109
193
|
} catch (error) {
|
|
110
194
|
if (installedNewPlugin) fs.rmSync(pluginRoot, { recursive: true, force: true });
|
|
111
|
-
if (hadPreviousPlugin && fs.existsSync(backupRoot)) {
|
|
112
|
-
fs.renameSync(backupRoot, pluginRoot);
|
|
113
|
-
}
|
|
114
195
|
throw error;
|
|
115
196
|
}
|
|
116
|
-
if (hadPreviousPlugin) fs.rmSync(backupRoot, { recursive: true, force: true });
|
|
117
197
|
} finally {
|
|
118
198
|
fs.rmSync(stagingRoot, { recursive: true, force: true });
|
|
119
199
|
}
|
|
120
200
|
|
|
121
|
-
return { installedFile, pluginRoot };
|
|
201
|
+
return { installedFile, pluginRoot, reused: false };
|
|
122
202
|
}
|
|
123
203
|
|
|
124
204
|
module.exports = {
|
package/bin/private-paths.js
CHANGED
|
@@ -11,6 +11,7 @@ const PRIVATE_TREE_MAX_DEPTH = 64;
|
|
|
11
11
|
const PRIVATE_TREE_MAX_ENTRIES = 4096;
|
|
12
12
|
const PRIVATE_TREE_MAX_PATH_LENGTH = 4096;
|
|
13
13
|
const WINDOWS_ACL_OK_SENTINEL = 'BLUN_PRIVATE_ACL_OK_V1';
|
|
14
|
+
const WINDOWS_ACL_ERROR_SENTINEL = 'BLUN_PRIVATE_ACL_ERROR_V1';
|
|
14
15
|
const WINDOWS_SID_PATTERN = /S-\d-\d+(?:-\d+)+/gi;
|
|
15
16
|
const WINDOWS_LOCAL_DOS_PATH_PATTERN = /^[A-Za-z]:\\/u;
|
|
16
17
|
const WINDOWS_RESERVED_PATH_SEGMENT = /^(?:CON|PRN|AUX|NUL|CONIN\$|CONOUT\$|COM[1-9]|LPT[1-9])(?:\..*)?$/iu;
|
|
@@ -1108,7 +1109,7 @@ const WINDOWS_ACL_SCRIPT = [
|
|
|
1108
1109
|
"[Console]::Out.Write('BLUN_PRIVATE_ACL_OK_V1')",
|
|
1109
1110
|
'exit 0',
|
|
1110
1111
|
'} catch {',
|
|
1111
|
-
"[Console]::Error.Write('BLUN_PRIVATE_ACL_ERROR_V1')",
|
|
1112
|
+
"[Console]::Error.Write('BLUN_PRIVATE_ACL_ERROR_V1|' + $_.Exception.GetType().FullName + '|' + $_.Exception.Message)",
|
|
1112
1113
|
'exit 1',
|
|
1113
1114
|
'}',
|
|
1114
1115
|
].join('\n');
|
|
@@ -1127,9 +1128,18 @@ function windowsSystemDirectory(options) {
|
|
|
1127
1128
|
return path.win32.normalize(directory);
|
|
1128
1129
|
}
|
|
1129
1130
|
|
|
1130
|
-
function windowsHostEnvironment(systemDirectory) {
|
|
1131
|
+
function windowsHostEnvironment(systemDirectory, env = process.env) {
|
|
1131
1132
|
const systemRoot = path.win32.dirname(systemDirectory);
|
|
1132
|
-
|
|
1133
|
+
const temporaryDirectory = env.TEMP || env.TMP;
|
|
1134
|
+
if (!temporaryDirectory) {
|
|
1135
|
+
throw new Error('Windows private ACL temporary directory is unavailable.');
|
|
1136
|
+
}
|
|
1137
|
+
return {
|
|
1138
|
+
SystemRoot: systemRoot,
|
|
1139
|
+
WINDIR: systemRoot,
|
|
1140
|
+
TEMP: temporaryDirectory,
|
|
1141
|
+
TMP: env.TMP || temporaryDirectory,
|
|
1142
|
+
};
|
|
1133
1143
|
}
|
|
1134
1144
|
|
|
1135
1145
|
function runWindowsCommand(executable, args, options, env, input) {
|
|
@@ -1190,14 +1200,14 @@ function resolveWindowsPrivateTarget(targetPath) {
|
|
|
1190
1200
|
throw new Error('Invalid Windows private path.');
|
|
1191
1201
|
}
|
|
1192
1202
|
return absoluteTarget;
|
|
1193
|
-
} catch {
|
|
1194
|
-
throw new Error('Windows private
|
|
1203
|
+
} catch (error) {
|
|
1204
|
+
throw new Error('Windows private path validation failed.', { cause: error });
|
|
1195
1205
|
}
|
|
1196
1206
|
}
|
|
1197
1207
|
|
|
1198
1208
|
function windowsAclController(options) {
|
|
1199
1209
|
const systemDirectory = windowsSystemDirectory(options);
|
|
1200
|
-
const hostEnvironment = windowsHostEnvironment(systemDirectory);
|
|
1210
|
+
const hostEnvironment = windowsHostEnvironment(systemDirectory, options.env || process.env);
|
|
1201
1211
|
const whoami = path.win32.join(systemDirectory, 'whoami.exe');
|
|
1202
1212
|
const powershell = path.win32.join(
|
|
1203
1213
|
systemDirectory,
|
|
@@ -1235,7 +1245,7 @@ function windowsAclController(options) {
|
|
|
1235
1245
|
if (!path.isAbsolute(absoluteTarget)
|
|
1236
1246
|
|| root.toLowerCase() === absoluteTarget.toLowerCase()
|
|
1237
1247
|
|| !['directory', 'file', 'file-write', 'tree'].includes(kind)) {
|
|
1238
|
-
throw new Error('Windows private ACL
|
|
1248
|
+
throw new Error('Windows private ACL target validation failed.');
|
|
1239
1249
|
}
|
|
1240
1250
|
const env = {
|
|
1241
1251
|
...hostEnvironment,
|
|
@@ -1253,11 +1263,21 @@ function windowsAclController(options) {
|
|
|
1253
1263
|
env,
|
|
1254
1264
|
input,
|
|
1255
1265
|
);
|
|
1256
|
-
} catch {
|
|
1257
|
-
|
|
1266
|
+
} catch (error) {
|
|
1267
|
+
const stderr = typeof error?.stderr === 'string'
|
|
1268
|
+
? error.stderr : Buffer.isBuffer(error?.stderr) ? error.stderr.toString('utf8') : '';
|
|
1269
|
+
const marker = `${WINDOWS_ACL_ERROR_SENTINEL}|`;
|
|
1270
|
+
const rawDetail = stderr.startsWith(marker) ? stderr.slice(marker.length) : '';
|
|
1271
|
+
const detail = rawDetail.replace(/[\u0000-\u001f\u007f]+/gu, ' ').trim().slice(0, 500);
|
|
1272
|
+
throw new Error(
|
|
1273
|
+
detail.length > 0
|
|
1274
|
+
? `Windows private ACL operation failed: ${detail}`
|
|
1275
|
+
: 'Windows private ACL operation failed.',
|
|
1276
|
+
{ cause: error },
|
|
1277
|
+
);
|
|
1258
1278
|
}
|
|
1259
1279
|
if (output !== WINDOWS_ACL_OK_SENTINEL) {
|
|
1260
|
-
throw new Error('Windows private ACL
|
|
1280
|
+
throw new Error('Windows private ACL verification returned an unexpected response.');
|
|
1261
1281
|
}
|
|
1262
1282
|
} finally {
|
|
1263
1283
|
input?.fill(0);
|
package/blun.mjs
CHANGED
|
@@ -235059,15 +235059,25 @@ async function discoverSkills(options) {
|
|
|
235059
235059
|
}
|
|
235060
235060
|
const directorySkills = /* @__PURE__ */ new Set();
|
|
235061
235061
|
const subdirs = [];
|
|
235062
|
-
|
|
235062
|
+
const classifiedEntries = await Promise.all(entries.map(async (entry) => {
|
|
235063
235063
|
const entryPath = posix$2.join(dirPath, entry);
|
|
235064
|
-
|
|
235065
|
-
|
|
235066
|
-
|
|
235064
|
+
const [hasSkillFile, directory] = await Promise.all([
|
|
235065
|
+
isFile(posix$2.join(entryPath, "SKILL.md")),
|
|
235066
|
+
entry === "node_modules" || entry.startsWith(".") ? false : isDir(entryPath)
|
|
235067
|
+
]);
|
|
235068
|
+
return {
|
|
235069
|
+
entry,
|
|
235070
|
+
hasSkillFile,
|
|
235071
|
+
directory
|
|
235072
|
+
};
|
|
235073
|
+
}));
|
|
235074
|
+
for (const classified of classifiedEntries) {
|
|
235075
|
+
if (classified.hasSkillFile) directorySkills.add(classified.entry);
|
|
235076
|
+
if (classified.directory) subdirs.push(classified.entry);
|
|
235067
235077
|
}
|
|
235068
235078
|
const allowedSubSkillBundles = /* @__PURE__ */ new Map();
|
|
235069
|
-
|
|
235070
|
-
|
|
235079
|
+
const directorySkillEntries = [...directorySkills];
|
|
235080
|
+
const directorySkillResults = await parseAndRegisterAll(directorySkillEntries.map((entry) => ({
|
|
235071
235081
|
parse,
|
|
235072
235082
|
byName,
|
|
235073
235083
|
skillMdPath: posix$2.join(dirPath, entry, "SKILL.md"),
|
|
@@ -235077,7 +235087,10 @@ async function discoverSkills(options) {
|
|
|
235077
235087
|
warn,
|
|
235078
235088
|
skip,
|
|
235079
235089
|
subSkillParentName
|
|
235080
|
-
});
|
|
235090
|
+
})));
|
|
235091
|
+
for (let index = 0; index < directorySkillEntries.length; index++) {
|
|
235092
|
+
const entry = directorySkillEntries[index];
|
|
235093
|
+
const skill = directorySkillResults[index];
|
|
235081
235094
|
if (skill !== void 0 && hasSubSkillEnabled(skill)) allowedSubSkillBundles.set(entry, skill.name);
|
|
235082
235095
|
}
|
|
235083
235096
|
if (isTopLevel) {
|
|
@@ -235094,6 +235107,7 @@ async function discoverSkills(options) {
|
|
|
235094
235107
|
skip
|
|
235095
235108
|
});
|
|
235096
235109
|
}
|
|
235110
|
+
const flatInputs = [];
|
|
235097
235111
|
for (const entry of entries) {
|
|
235098
235112
|
if (!entry.endsWith(".md")) continue;
|
|
235099
235113
|
if (entry === "SKILL.md") continue;
|
|
@@ -235104,7 +235118,7 @@ async function discoverSkills(options) {
|
|
|
235104
235118
|
}
|
|
235105
235119
|
const skillMdPath = posix$2.join(dirPath, entry);
|
|
235106
235120
|
if (!await isFile(skillMdPath)) continue;
|
|
235107
|
-
|
|
235121
|
+
flatInputs.push({
|
|
235108
235122
|
parse,
|
|
235109
235123
|
byName,
|
|
235110
235124
|
skillMdPath,
|
|
@@ -235115,6 +235129,7 @@ async function discoverSkills(options) {
|
|
|
235115
235129
|
skip
|
|
235116
235130
|
});
|
|
235117
235131
|
}
|
|
235132
|
+
await parseAndRegisterAll(flatInputs);
|
|
235118
235133
|
}
|
|
235119
235134
|
for (const entry of subdirs) {
|
|
235120
235135
|
if (directorySkills.has(entry) && !allowedSubSkillBundles.has(entry)) continue;
|
|
@@ -235181,7 +235196,7 @@ async function pushProvidedRoot(out, root, isDir, realpath) {
|
|
|
235181
235196
|
};
|
|
235182
235197
|
return true;
|
|
235183
235198
|
}
|
|
235184
|
-
async function
|
|
235199
|
+
async function parseSkillCandidate(input) {
|
|
235185
235200
|
try {
|
|
235186
235201
|
const parsed = await input.parse({
|
|
235187
235202
|
skillMdPath: input.skillMdPath,
|
|
@@ -235201,9 +235216,6 @@ async function parseAndRegister(input) {
|
|
|
235201
235216
|
...skill,
|
|
235202
235217
|
plugin: input.root.plugin
|
|
235203
235218
|
};
|
|
235204
|
-
input.onDiscoveredSkill?.(discovered);
|
|
235205
|
-
const key = normalizeSkillName(discovered.name);
|
|
235206
|
-
if (!input.byName.has(key)) input.byName.set(key, discovered);
|
|
235207
235219
|
return discovered;
|
|
235208
235220
|
} catch (error) {
|
|
235209
235221
|
if (error instanceof UnsupportedSkillTypeError) input.skip({
|
|
@@ -235216,6 +235228,20 @@ async function parseAndRegister(input) {
|
|
|
235216
235228
|
return;
|
|
235217
235229
|
}
|
|
235218
235230
|
}
|
|
235231
|
+
function registerSkillCandidate(input, discovered) {
|
|
235232
|
+
if (discovered === void 0) return;
|
|
235233
|
+
input.onDiscoveredSkill?.(discovered);
|
|
235234
|
+
const key = normalizeSkillName(discovered.name);
|
|
235235
|
+
if (!input.byName.has(key)) input.byName.set(key, discovered);
|
|
235236
|
+
return discovered;
|
|
235237
|
+
}
|
|
235238
|
+
async function parseAndRegister(input) {
|
|
235239
|
+
return registerSkillCandidate(input, await parseSkillCandidate(input));
|
|
235240
|
+
}
|
|
235241
|
+
async function parseAndRegisterAll(inputs) {
|
|
235242
|
+
const discovered = await Promise.all(inputs.map((input) => parseSkillCandidate(input)));
|
|
235243
|
+
return discovered.map((skill, index) => registerSkillCandidate(inputs[index], skill));
|
|
235244
|
+
}
|
|
235219
235245
|
function qualifySubSkillName(parentName, skillName) {
|
|
235220
235246
|
if (skillName === parentName || skillName.startsWith(`${parentName}.`)) return skillName;
|
|
235221
235247
|
return `${parentName}.${skillName}`;
|
|
@@ -260655,7 +260681,7 @@ var init_turn = __esmMin((() => {
|
|
|
260655
260681
|
this.agent.log.warn("goal token accounting failed", { error });
|
|
260656
260682
|
}
|
|
260657
260683
|
},
|
|
260658
|
-
|
|
260684
|
+
hooks: {
|
|
260659
260685
|
beforeStep: async ({ signal: stepSignal, llm }) => {
|
|
260660
260686
|
this.agent.microCompaction.detect();
|
|
260661
260687
|
await this.agent.injection.inject();
|
|
@@ -260705,6 +260731,8 @@ var init_turn = __esmMin((() => {
|
|
|
260705
260731
|
return { continue: false };
|
|
260706
260732
|
},
|
|
260707
260733
|
prepareToolExecution: async (ctx) => {
|
|
260734
|
+
const ideaPolicy = enforceIdeaToolPolicy(this.agent, ctx);
|
|
260735
|
+
if (ideaPolicy !== void 0) return ideaPolicy;
|
|
260708
260736
|
const cached = deduper.checkSameStep(ctx.toolCall.id, ctx.toolCall.name, ctx.args);
|
|
260709
260737
|
if (cached !== null) return { syntheticResult: cached };
|
|
260710
260738
|
},
|
|
@@ -262008,6 +262036,14 @@ function isMediaToolName(name) {
|
|
|
262008
262036
|
"GetMedia"
|
|
262009
262037
|
].includes(name);
|
|
262010
262038
|
}
|
|
262039
|
+
const MEDIA_INLINE_PREVIEW_MAX_BYTES = 1536 * 1024;
|
|
262040
|
+
function mediaInlinePreviewPayload(data, mimeType) {
|
|
262041
|
+
if (data.byteLength === 0 || data.byteLength > MEDIA_INLINE_PREVIEW_MAX_BYTES) return {};
|
|
262042
|
+
return {
|
|
262043
|
+
previewDataBase64: Buffer.from(data.buffer, data.byteOffset, data.byteLength).toString("base64"),
|
|
262044
|
+
previewMimeType: mimeType
|
|
262045
|
+
};
|
|
262046
|
+
}
|
|
262011
262047
|
function emitMediaProgress(ctx, toolName, phase, data = {}) {
|
|
262012
262048
|
ctx.onUpdate({
|
|
262013
262049
|
kind: "custom",
|
|
@@ -262161,6 +262197,7 @@ var init_blun_media$1 = __esmMin((() => {
|
|
|
262161
262197
|
isError: true,
|
|
262162
262198
|
output: `"${args.path}" is empty.`
|
|
262163
262199
|
};
|
|
262200
|
+
emitMediaProgress(ctx, this.name, "uploading", mediaInlinePreviewPayload(data, fileType.mimeType));
|
|
262164
262201
|
const options = {
|
|
262165
262202
|
signal: ctx.signal,
|
|
262166
262203
|
toolCallId: ctx.toolCallId
|
|
@@ -310932,6 +310969,14 @@ function mediaPayloadNumberArray(payload, keys) {
|
|
|
310932
310969
|
if (samples.length > 0) return samples.slice(0, 512);
|
|
310933
310970
|
}
|
|
310934
310971
|
}
|
|
310972
|
+
function mediaPayloadTextArray(payload, keys) {
|
|
310973
|
+
for (const key of keys) {
|
|
310974
|
+
const value = payload[key];
|
|
310975
|
+
if (!Array.isArray(value)) continue;
|
|
310976
|
+
const entries = value.filter((entry) => typeof entry === "string").map((entry) => entry.trim()).filter((entry) => entry.length > 0);
|
|
310977
|
+
if (entries.length > 0) return entries.slice(-32);
|
|
310978
|
+
}
|
|
310979
|
+
}
|
|
310935
310980
|
function mediaProgressFromPayload(payload) {
|
|
310936
310981
|
const nestedProgress = payload["progress"];
|
|
310937
310982
|
const source = typeof nestedProgress === "object" && nestedProgress !== null && !Array.isArray(nestedProgress) ? {
|
|
@@ -310962,7 +311007,8 @@ function mediaProgressFromPayload(payload) {
|
|
|
310962
311007
|
previewMimeType: mediaPayloadText(source, ["preview_mime_type", "previewMimeType", "thumbnail_mime_type", "thumbnailMimeType"]),
|
|
310963
311008
|
sampleUrl: mediaPayloadText(source, ["sample_url", "sampleUrl", "audio_preview_url", "audioPreviewUrl"]),
|
|
310964
311009
|
localPath: mediaPayloadText(source, ["local_path", "localPath"]),
|
|
310965
|
-
waveform: mediaPayloadNumberArray(source, ["waveform", "waveform_samples", "waveformSamples"])
|
|
311010
|
+
waveform: mediaPayloadNumberArray(source, ["waveform", "waveform_samples", "waveformSamples"]),
|
|
311011
|
+
phaseHistory: mediaPayloadTextArray(source, ["phase_history", "phaseHistory"])
|
|
310966
311012
|
};
|
|
310967
311013
|
return Object.fromEntries(Object.entries(progress).filter(([, value]) => value !== void 0));
|
|
310968
311014
|
}
|
|
@@ -400692,6 +400738,7 @@ registerUiCatalogFragment({
|
|
|
400692
400738
|
"command.reload-tui.description": "Reload interface settings only",
|
|
400693
400739
|
"command.compact.description": "Compact the conversation context",
|
|
400694
400740
|
"command.goal.description": "Start or manage an autonomous goal",
|
|
400741
|
+
"command.idea.description": "Start or manage an autonomous idea",
|
|
400695
400742
|
"command.loop.description": "Start or manage a recurring prompt",
|
|
400696
400743
|
"command.init.description": "Analyze the codebase and generate AGENTS.md",
|
|
400697
400744
|
"command.fork.description": "Fork the current session",
|
|
@@ -400749,6 +400796,7 @@ registerUiCatalogFragment({
|
|
|
400749
400796
|
"command.reload-tui.description": "Nur Oberflächeneinstellungen neu laden",
|
|
400750
400797
|
"command.compact.description": "Konversationskontext komprimieren",
|
|
400751
400798
|
"command.goal.description": "Autonomes Ziel starten oder verwalten",
|
|
400799
|
+
"command.idea.description": "Autonome Idee starten oder verwalten",
|
|
400752
400800
|
"command.loop.description": "Wiederkehrenden Prompt starten oder verwalten",
|
|
400753
400801
|
"command.init.description": "Codebasis analysieren und AGENTS.md erstellen",
|
|
400754
400802
|
"command.fork.description": "Aktuelle Sitzung abzweigen",
|
|
@@ -400806,6 +400854,7 @@ registerUiCatalogFragment({
|
|
|
400806
400854
|
"command.reload-tui.description": "Recargar solo los ajustes de la interfaz",
|
|
400807
400855
|
"command.compact.description": "Compactar el contexto de la conversación",
|
|
400808
400856
|
"command.goal.description": "Iniciar o gestionar un objetivo autónomo",
|
|
400857
|
+
"command.idea.description": "Iniciar o gestionar una idea autónoma",
|
|
400809
400858
|
"command.loop.description": "Iniciar o gestionar una instrucción recurrente",
|
|
400810
400859
|
"command.init.description": "Analizar el código base y generar AGENTS.md",
|
|
400811
400860
|
"command.fork.description": "Bifurcar la sesión actual",
|
|
@@ -400863,6 +400912,7 @@ registerUiCatalogFragment({
|
|
|
400863
400912
|
"command.reload-tui.description": "Recharger uniquement les paramètres de l’interface",
|
|
400864
400913
|
"command.compact.description": "Compacter le contexte de la conversation",
|
|
400865
400914
|
"command.goal.description": "Démarrer ou gérer un objectif autonome",
|
|
400915
|
+
"command.idea.description": "Démarrer ou gérer une idée autonome",
|
|
400866
400916
|
"command.loop.description": "Démarrer ou gérer une demande récurrente",
|
|
400867
400917
|
"command.init.description": "Analyser la base de code et générer AGENTS.md",
|
|
400868
400918
|
"command.fork.description": "Créer une branche de la session actuelle",
|
|
@@ -400920,6 +400970,7 @@ registerUiCatalogFragment({
|
|
|
400920
400970
|
"command.reload-tui.description": "Läs bara in gränssnittsinställningarna på nytt",
|
|
400921
400971
|
"command.compact.description": "Komprimera konversationskontexten",
|
|
400922
400972
|
"command.goal.description": "Starta eller hantera ett autonomt mål",
|
|
400973
|
+
"command.idea.description": "Starta eller hantera en självständig idé",
|
|
400923
400974
|
"command.loop.description": "Starta eller hantera en återkommande prompt",
|
|
400924
400975
|
"command.init.description": "Analysera kodbasen och skapa AGENTS.md",
|
|
400925
400976
|
"command.fork.description": "Förgrena den aktuella sessionen",
|
|
@@ -400977,6 +401028,7 @@ registerUiCatalogFragment({
|
|
|
400977
401028
|
"command.reload-tui.description": "Znovu načíst pouze nastavení rozhraní",
|
|
400978
401029
|
"command.compact.description": "Komprimovat kontext konverzace",
|
|
400979
401030
|
"command.goal.description": "Spustit nebo spravovat autonomní cíl",
|
|
401031
|
+
"command.idea.description": "Spustit nebo spravovat autonomní nápad",
|
|
400980
401032
|
"command.loop.description": "Spustit nebo spravovat opakované zadání",
|
|
400981
401033
|
"command.init.description": "Analyzovat kódovou základnu a vygenerovat AGENTS.md",
|
|
400982
401034
|
"command.fork.description": "Vytvořit větev aktuální relace",
|
|
@@ -401389,7 +401441,22 @@ const BUILTIN_SLASH_COMMAND_DEFINITIONS = [
|
|
|
401389
401441
|
{
|
|
401390
401442
|
name: "idea",
|
|
401391
401443
|
aliases: [],
|
|
401392
|
-
descriptionKey: "command.
|
|
401444
|
+
descriptionKey: "command.idea.description",
|
|
401445
|
+
priority: 80,
|
|
401446
|
+
argumentHint: "<objective>",
|
|
401447
|
+
availability: (args) => {
|
|
401448
|
+
const trimmed = args.trim();
|
|
401449
|
+
if (trimmed === "") return "always";
|
|
401450
|
+
const tokens = trimmed.split(/\s+/);
|
|
401451
|
+
const action = tokens[0]?.toLowerCase();
|
|
401452
|
+
if (tokens.length === 1 && (action === "status" || action === "pause" || action === "paused" || action === "resume" || action === "cancel" || action === "clear" || action === "stop")) return "always";
|
|
401453
|
+
return "idle-only";
|
|
401454
|
+
}
|
|
401455
|
+
},
|
|
401456
|
+
{
|
|
401457
|
+
name: "chancenradar",
|
|
401458
|
+
aliases: [],
|
|
401459
|
+
descriptionKey: "command.idea.description",
|
|
401393
401460
|
priority: 80,
|
|
401394
401461
|
argumentHint: "<objective>",
|
|
401395
401462
|
availability: (args) => {
|
|
@@ -416690,7 +416757,7 @@ function bufferContext(state, chatId, tag) {
|
|
|
416690
416757
|
function channelOrigin(envelope) {
|
|
416691
416758
|
return `Telegram · ${envelope.meta.user ?? envelope.meta.chat_id}`;
|
|
416692
416759
|
}
|
|
416693
|
-
const TELEGRAM_REMOTE_COMMANDS = Object.freeze(["loop", "goal", "idea", "befehle"]);
|
|
416760
|
+
const TELEGRAM_REMOTE_COMMANDS = Object.freeze(["loop", "goal", "idea", "chancenradar", "befehle"]);
|
|
416694
416761
|
function telegramRemoteCommand(text) {
|
|
416695
416762
|
const trimmed = text.trim();
|
|
416696
416763
|
const parsed = parseSlashInput(trimmed);
|
|
@@ -416904,19 +416971,25 @@ function sessionMcpServers() {
|
|
|
416904
416971
|
return telegramChannelMcpServers();
|
|
416905
416972
|
}
|
|
416906
416973
|
function managedTelegramPluginEnabled() {
|
|
416974
|
+
return managedTelegramPluginRoot() !== void 0;
|
|
416975
|
+
}
|
|
416976
|
+
function managedTelegramPluginRoot() {
|
|
416907
416977
|
const blunHome = join(homedir(), ".blun");
|
|
416908
|
-
const managedRoot = join(blunHome, "plugins", "managed", "telegram");
|
|
416909
|
-
if (!existsSync(join(managedRoot, "dist", "mcp-server.mjs"))) return false;
|
|
416910
416978
|
try {
|
|
416911
|
-
|
|
416979
|
+
const telegram = JSON.parse(readFileSync(join(blunHome, "plugins", "installed.json"), "utf8")).plugins?.find((plugin) => plugin.id === "telegram" && plugin.enabled === true);
|
|
416980
|
+
if (typeof telegram?.root !== "string" || telegram.root.length === 0) return void 0;
|
|
416981
|
+
if (!existsSync(join(telegram.root, "dist", "mcp-server.mjs"))) return void 0;
|
|
416982
|
+
return telegram.root;
|
|
416912
416983
|
} catch {
|
|
416913
|
-
return
|
|
416984
|
+
return void 0;
|
|
416914
416985
|
}
|
|
416915
416986
|
}
|
|
416916
416987
|
function resolveTelegramMcpEntry() {
|
|
416917
416988
|
const main = process.argv[1];
|
|
416989
|
+
const managedRoot = managedTelegramPluginRoot();
|
|
416918
416990
|
const candidates = [
|
|
416919
416991
|
process.env["BLUN_TELEGRAM_MCP_SERVER"],
|
|
416992
|
+
managedRoot === void 0 ? void 0 : join(managedRoot, "dist", "mcp-server.mjs"),
|
|
416920
416993
|
join(homedir(), ".blun", "plugins", "managed", "telegram", "dist", "mcp-server.mjs"),
|
|
416921
416994
|
main === void 0 ? void 0 : join(dirname(main), "..", "..", "..", "plugins", "telegram", "dist", "mcp-server.mjs")
|
|
416922
416995
|
];
|
|
@@ -417322,8 +417395,10 @@ var TelegramChannelController = class {
|
|
|
417322
417395
|
}
|
|
417323
417396
|
resolveBridgeEntry() {
|
|
417324
417397
|
const main = process.argv[1];
|
|
417398
|
+
const managedRoot = managedTelegramPluginRoot();
|
|
417325
417399
|
const candidates = [
|
|
417326
417400
|
process.env["BLUN_TELEGRAM_BRIDGE"],
|
|
417401
|
+
managedRoot === void 0 ? void 0 : join(managedRoot, "dist", "bridge.mjs"),
|
|
417327
417402
|
join(homedir(), ".blun", "plugins", "managed", "telegram", "dist", "bridge.mjs"),
|
|
417328
417403
|
main === void 0 ? void 0 : join(dirname(main), "..", "..", "..", "plugins", "telegram", "dist", "bridge.mjs")
|
|
417329
417404
|
];
|
|
@@ -420130,6 +420205,7 @@ async function handleGoalCommand(host, args) {
|
|
|
420130
420205
|
return;
|
|
420131
420206
|
}
|
|
420132
420207
|
}
|
|
420208
|
+
const IDEA_CONTRACT_MARKER = "Work as a self-directing employee:";
|
|
420133
420209
|
const IDEA_ALLOWED_CHANNELS = Object.freeze([]);
|
|
420134
420210
|
const IDEA_TERMINAL_TODO_STATUSES = Object.freeze([
|
|
420135
420211
|
"done",
|
|
@@ -420137,6 +420213,77 @@ const IDEA_TERMINAL_TODO_STATUSES = Object.freeze([
|
|
|
420137
420213
|
"waiting_approval",
|
|
420138
420214
|
"aborted"
|
|
420139
420215
|
]);
|
|
420216
|
+
const IDEA_OUTBOUND_TOOL_LEAF = /(?:^|_)(?:send|reply|post|publish|upload|purchase|buy|register|signup|merge|push|deploy|create_(?:account|user|issue|pull|pr|comment|post|order|campaign|repo(?:sitory)?)|update_(?:issue|pull|pr|comment|post)|delete_(?:issue|pull|pr|comment|post|account|user)|release)(?:_|$)/iu;
|
|
420217
|
+
const IDEA_OUTBOUND_SHELL = /(?:\bgit\s+push\b|\bnpm\s+publish\b|\bgh\s+(?:pr|issue|release)\s+(?:create|edit|comment|merge|delete)\b|\b(?:ssh|scp|rsync)\b|\bSend-MailMessage\b|\b(?:curl|wget)\b[^\r\n]*(?:-X|--request)\s*(?:POST|PUT|PATCH|DELETE)\b|\bInvoke-(?:WebRequest|RestMethod)\b[^\r\n]*-Method\s+(?:POST|PUT|PATCH|DELETE)\b)/iu;
|
|
420218
|
+
function isIdeaGoal(agent) {
|
|
420219
|
+
const goal = agent.goal.getActiveGoal();
|
|
420220
|
+
return goal !== null && goal.completionCriterion?.includes(IDEA_CONTRACT_MARKER) === true;
|
|
420221
|
+
}
|
|
420222
|
+
function ideaTodos(agent) {
|
|
420223
|
+
const value = agent.tools.store?.["todo"];
|
|
420224
|
+
return Array.isArray(value) ? value : [];
|
|
420225
|
+
}
|
|
420226
|
+
function validInitialIdeaPlan(value) {
|
|
420227
|
+
if (!Array.isArray(value) || value.length === 0) return false;
|
|
420228
|
+
let active = 0;
|
|
420229
|
+
for (const todo of value) {
|
|
420230
|
+
if (typeof todo !== "object" || todo === null || typeof todo.title !== "string" || todo.title.trim().length === 0) return false;
|
|
420231
|
+
if (todo.status === "in_progress") active += 1;
|
|
420232
|
+
else if (todo.status !== "pending") return false;
|
|
420233
|
+
}
|
|
420234
|
+
return active === 1;
|
|
420235
|
+
}
|
|
420236
|
+
function ideaOutboundChannel(toolName, args) {
|
|
420237
|
+
if (toolName === "Bash") {
|
|
420238
|
+
const command = typeof args?.command === "string" ? args.command : "";
|
|
420239
|
+
return IDEA_OUTBOUND_SHELL.test(command) ? "shell-external" : void 0;
|
|
420240
|
+
}
|
|
420241
|
+
if (!toolName.startsWith("mcp__")) return void 0;
|
|
420242
|
+
const lower = toolName.toLowerCase();
|
|
420243
|
+
if (lower.includes("blun_language_guard")) return void 0;
|
|
420244
|
+
const leaf = lower.split("__").at(-1) ?? lower;
|
|
420245
|
+
if (!IDEA_OUTBOUND_TOOL_LEAF.test(leaf)) return void 0;
|
|
420246
|
+
for (const channel of ["telegram", "github", "reddit", "linkedin", "instagram", "facebook", "youtube", "gmail", "email", "smtp", "canva"]) if (lower.includes(channel)) return channel;
|
|
420247
|
+
return lower.split("__")[1] ?? "external";
|
|
420248
|
+
}
|
|
420249
|
+
function enforceIdeaToolPolicy(agent, context) {
|
|
420250
|
+
if (!isIdeaGoal(agent)) return;
|
|
420251
|
+
const todos = ideaTodos(agent);
|
|
420252
|
+
const batchKey = `${String(context.turnId)}:${String(context.stepNumber)}`;
|
|
420253
|
+
if (todos.length === 0) {
|
|
420254
|
+
const firstCall = context.toolCalls[0];
|
|
420255
|
+
if (context.toolCall.id !== firstCall?.id || context.toolCall.name !== "TodoList") return {
|
|
420256
|
+
block: true,
|
|
420257
|
+
reason: "Idea mode requires TodoList to be the first tool call. Publish the visible plan before research or action."
|
|
420258
|
+
};
|
|
420259
|
+
if (!validInitialIdeaPlan(context.args?.todos)) return {
|
|
420260
|
+
block: true,
|
|
420261
|
+
reason: "The first Idea TodoList must contain at least one item, exactly one in_progress item, and only pending items otherwise."
|
|
420262
|
+
};
|
|
420263
|
+
agent.ideaPlanBatchKey = batchKey;
|
|
420264
|
+
return;
|
|
420265
|
+
}
|
|
420266
|
+
if (agent.ideaPlanBatchKey === batchKey && context.toolCall.name !== "TodoList") return {
|
|
420267
|
+
block: true,
|
|
420268
|
+
reason: "Idea mode publishes its plan in a separate first step. Continue research or action in the next step."
|
|
420269
|
+
};
|
|
420270
|
+
if (context.toolCall.name === "UpdateGoal" && (context.args?.status === "complete" || context.args?.status === "blocked")) {
|
|
420271
|
+
if (todos.some((todo) => !IDEA_TERMINAL_TODO_STATUSES.includes(todo.status))) return {
|
|
420272
|
+
block: true,
|
|
420273
|
+
reason: "Idea mode cannot finish while a plan item has no truthful terminal status."
|
|
420274
|
+
};
|
|
420275
|
+
if (context.args.status === "complete" && todos.some((todo) => todo.status !== "done")) return {
|
|
420276
|
+
block: true,
|
|
420277
|
+
reason: "Idea mode can complete only when every plan item is done. Use blocked when any item is blocked, waiting_approval, or aborted."
|
|
420278
|
+
};
|
|
420279
|
+
}
|
|
420280
|
+
const channel = ideaOutboundChannel(context.toolCall.name, context.args);
|
|
420281
|
+
if (channel === void 0 || IDEA_ALLOWED_CHANNELS.includes(channel)) return;
|
|
420282
|
+
return {
|
|
420283
|
+
block: true,
|
|
420284
|
+
reason: `Idea mode blocked an external action on channel "${channel}". Prepare the result, mark the step waiting_approval with the required access or approval, and ask the user.`
|
|
420285
|
+
};
|
|
420286
|
+
}
|
|
420140
420287
|
function buildIdeaPrompt(objective) {
|
|
420141
420288
|
const allowedChannels = IDEA_ALLOWED_CHANNELS.length === 0 ? "none" : IDEA_ALLOWED_CHANNELS.join(", ");
|
|
420142
420289
|
return `<idea-mode>
|
|
@@ -420149,7 +420296,7 @@ ${buildIdeaContract(allowedChannels)}
|
|
|
420149
420296
|
</idea-mode>`;
|
|
420150
420297
|
}
|
|
420151
420298
|
function buildIdeaContract(allowedChannels = IDEA_ALLOWED_CHANNELS.length === 0 ? "none" : IDEA_ALLOWED_CHANNELS.join(", ")) {
|
|
420152
|
-
return
|
|
420299
|
+
return `${IDEA_CONTRACT_MARKER} discover a useful route, research current examples and real user problems when relevant, decide, implement reversible local work, verify results, and keep going without waiting for step-by-step instructions.
|
|
420153
420300
|
|
|
420154
420301
|
Maintain the task-specific plan in TodoList. Keep exactly one item in_progress. Every plan item must end in exactly one truthful terminal status from: ${IDEA_TERMINAL_TODO_STATUSES.join(", ")}. A blocked title must include the reason and what would unblock it. A waiting_approval title must include the prepared result and the exact approval needed. An aborted title must include the last reached state.
|
|
420155
420302
|
|
|
@@ -420175,6 +420322,42 @@ async function handleIdeaCommand(host, args) {
|
|
|
420175
420322
|
}
|
|
420176
420323
|
});
|
|
420177
420324
|
}
|
|
420325
|
+
const CHANCE_RADAR_CONTRACT_MARKER = "Operate as an evidence-driven opportunity radar:";
|
|
420326
|
+
function buildChanceRadarContract() {
|
|
420327
|
+
return `${buildIdeaContract()}
|
|
420328
|
+
|
|
420329
|
+
${CHANCE_RADAR_CONTRACT_MARKER} search current public sources, relevant forums, communities, reviews, and competing approaches for concrete unmet needs. Prefer multiple independent sources when available. Preserve the source URL and retrieval date for material evidence. Never invent demand, users, quotes, reach, or market size.
|
|
420330
|
+
|
|
420331
|
+
Separate observations, inferences, and unknowns. For every serious opportunity record the problem, affected people, evidence, existing alternatives, fit with the user's objective, likely impact, implementation effort, and confidence. Justify every ranking; a numeric score without evidence is not a finding. Drop weak or duplicated opportunities instead of padding the list.
|
|
420332
|
+
|
|
420333
|
+
Choose the strongest supported opportunity, update TodoList with the decision, and execute the best safe local next step. Ask a focused question only when the answer changes the ranking or next action. Keep all Idea-mode approval and access boundaries in force.`;
|
|
420334
|
+
}
|
|
420335
|
+
function buildChanceRadarPrompt(objective) {
|
|
420336
|
+
return `<chance-radar-mode>
|
|
420337
|
+
The user's objective is:
|
|
420338
|
+
${objective}
|
|
420339
|
+
|
|
420340
|
+
Your first tool call MUST be TodoList. Publish a task-specific discovery, research, ranking, and execution plan before any research or action.
|
|
420341
|
+
|
|
420342
|
+
${buildChanceRadarContract()}
|
|
420343
|
+
</chance-radar-mode>`;
|
|
420344
|
+
}
|
|
420345
|
+
async function handleChanceRadarCommand(host, args) {
|
|
420346
|
+
const parsed = parseGoalCommand(args);
|
|
420347
|
+
if (parsed.kind !== "create") {
|
|
420348
|
+
await handleGoalCommand(host, args);
|
|
420349
|
+
return;
|
|
420350
|
+
}
|
|
420351
|
+
await createGoal(host, {
|
|
420352
|
+
...parsed,
|
|
420353
|
+
completionCriterion: buildChanceRadarContract()
|
|
420354
|
+
}, args, {
|
|
420355
|
+
commandName: "chancenradar",
|
|
420356
|
+
sendInput: (objective) => {
|
|
420357
|
+
host.sendMessage(host.requireSession(), objective, { parts: buildChanceRadarPrompt(objective) });
|
|
420358
|
+
}
|
|
420359
|
+
});
|
|
420360
|
+
}
|
|
420178
420361
|
function parseNextGoalCommand(tokens) {
|
|
420179
420362
|
if (tokens.length === 2 && tokens[1]?.toLowerCase() === "manage") return { kind: "next-manage" };
|
|
420180
420363
|
let index = 1;
|
|
@@ -493192,6 +493375,9 @@ async function handleBuiltInSlashCommand(host, name, args) {
|
|
|
493192
493375
|
case "idea":
|
|
493193
493376
|
await handleIdeaCommand(host, args);
|
|
493194
493377
|
return;
|
|
493378
|
+
case "chancenradar":
|
|
493379
|
+
await handleChanceRadarCommand(host, args);
|
|
493380
|
+
return;
|
|
493195
493381
|
case "loop":
|
|
493196
493382
|
await handleLoopCommand(host, args);
|
|
493197
493383
|
return;
|
|
@@ -498482,6 +498668,10 @@ var MediaActivityStore = class {
|
|
|
498482
498668
|
const mediaKind = event.mediaKind === "media" && previous?.mediaKind !== void 0 ? previous.mediaKind : event.mediaKind ?? previous?.mediaKind ?? "media";
|
|
498483
498669
|
const phase = mediaActivityStatusKey(event.phase ?? previous?.phase ?? "processing");
|
|
498484
498670
|
const phaseHistory = [...previous?.phaseHistory ?? []];
|
|
498671
|
+
if (Array.isArray(event.phaseHistory)) for (const reportedPhase of event.phaseHistory) {
|
|
498672
|
+
const normalized = mediaActivityStatusKey(reportedPhase);
|
|
498673
|
+
if (phaseHistory.at(-1) !== normalized) phaseHistory.push(normalized);
|
|
498674
|
+
}
|
|
498485
498675
|
if (phaseHistory.at(-1) !== phase) phaseHistory.push(phase);
|
|
498486
498676
|
this.jobs.set(key, {
|
|
498487
498677
|
...previous,
|
|
@@ -514009,7 +514199,7 @@ var BlunTUI = class {
|
|
|
514009
514199
|
};
|
|
514010
514200
|
this.telegramRemoteCommandContext = context;
|
|
514011
514201
|
const execution = item.telegramCommandName === "befehle" ? Promise.resolve().then(() => {
|
|
514012
|
-
context.responses.push("/loop\n/goal\n/idea\n/befehle");
|
|
514202
|
+
context.responses.push("/loop\n/goal\n/idea\n/chancenradar\n/befehle");
|
|
514013
514203
|
}) : dispatchInput(this, item.text);
|
|
514014
514204
|
execution.catch((error) => {
|
|
514015
514205
|
this.showError(formatErrorMessage$2(error));
|