blun-king-cli 9.1.53 → 9.1.58
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 +35 -4
- package/README.md +35 -4
- package/bin/plugin-bootstrap.js +95 -15
- package/bin/private-paths.js +34 -11
- package/blun.mjs +130 -20
- 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
|
|
@@ -63,8 +83,16 @@ Der erste Werkzeugaufruf muss den sichtbaren Plan setzen. Ausgehende Aktionen
|
|
|
63
83
|
werden zusätzlich pro Werkzeugaufruf geprüft und bei fehlender Kanal- oder
|
|
64
84
|
Zugangsfreigabe angehalten.
|
|
65
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
|
+
|
|
66
94
|
Im automatisch angebundenen Telegram-Kanal werden ausschließlich /loop, /goal,
|
|
67
|
-
/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
|
|
68
96
|
sie in der Warteschlange. Andere Slash-Eingaben werden aus Sicherheitsgründen
|
|
69
97
|
als normale Chatnachrichten behandelt.
|
|
70
98
|
|
|
@@ -91,8 +119,11 @@ anschließend mit GetMedia abgerufen.
|
|
|
91
119
|
Die Medienleiste erscheint nur bei aktiven Medienaufträgen. Sie zeigt
|
|
92
120
|
Warteschlangenplatz, Produktionsphase und Laufzeit sowie nur tatsächlich vom
|
|
93
121
|
Anbieter gemeldete Prozentwerte, Schritte, Frames, FPS, Audiolänge, Restzeit,
|
|
94
|
-
Auflösung, Wellenform und Vorschauen.
|
|
95
|
-
|
|
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.
|
|
96
127
|
|
|
97
128
|
Aktualisieren
|
|
98
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
|
|
@@ -78,8 +98,16 @@ Der erste Werkzeugaufruf muss den sichtbaren Plan setzen. Ausgehende Aktionen
|
|
|
78
98
|
werden zusätzlich pro Werkzeugaufruf geprüft und bei fehlender Kanal- oder
|
|
79
99
|
Zugangsfreigabe angehalten.
|
|
80
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
|
+
|
|
81
109
|
Im automatisch angebundenen Telegram-Kanal werden ausschließlich `/loop`,
|
|
82
|
-
`/goal`, `/idea` und `/befehle` als Befehle ausgeführt. Ist der Agent
|
|
110
|
+
`/goal`, `/idea`, `/chancenradar` und `/befehle` als Befehle ausgeführt. Ist der Agent
|
|
83
111
|
beschäftigt, bleiben sie in der Warteschlange. Andere Slash-Eingaben werden aus
|
|
84
112
|
Sicherheitsgründen als normale Chatnachrichten behandelt.
|
|
85
113
|
|
|
@@ -112,8 +140,11 @@ anschließend mit `GetMedia` abgerufen.
|
|
|
112
140
|
Die Medienleiste erscheint nur bei aktiven Medienaufträgen. Sie zeigt
|
|
113
141
|
Warteschlangenplatz, Produktionsphase und Laufzeit sowie nur tatsächlich vom
|
|
114
142
|
Anbieter gemeldete Prozentwerte, Schritte, Frames, FPS, Audiolänge, Restzeit,
|
|
115
|
-
Auflösung, Wellenform und Vorschauen.
|
|
116
|
-
|
|
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.
|
|
117
148
|
|
|
118
149
|
## Aktualisieren
|
|
119
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;
|
|
@@ -33,6 +34,9 @@ public static class BlunPrivateAclNative
|
|
|
33
34
|
private const uint READ_CONTROL = 0x00020000;
|
|
34
35
|
private const uint WRITE_DAC = 0x00040000;
|
|
35
36
|
private const uint SYNCHRONIZE = 0x00100000;
|
|
37
|
+
private const uint FILE_SHARE_READ = 0x00000001;
|
|
38
|
+
private const uint FILE_SHARE_WRITE = 0x00000002;
|
|
39
|
+
private const uint FILE_SHARE_DELETE = 0x00000004;
|
|
36
40
|
private const uint CREATE_NEW = 1;
|
|
37
41
|
private const uint OPEN_EXISTING = 3;
|
|
38
42
|
private const uint FILE_ATTRIBUTE_NORMAL = 0x00000080;
|
|
@@ -607,7 +611,7 @@ public static class BlunPrivateAclNative
|
|
|
607
611
|
out ioStatus,
|
|
608
612
|
IntPtr.Zero,
|
|
609
613
|
0,
|
|
610
|
-
|
|
614
|
+
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
|
|
611
615
|
FILE_OPEN_IF,
|
|
612
616
|
FILE_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT | FILE_OPEN_REPARSE_POINT,
|
|
613
617
|
IntPtr.Zero,
|
|
@@ -1108,7 +1112,7 @@ const WINDOWS_ACL_SCRIPT = [
|
|
|
1108
1112
|
"[Console]::Out.Write('BLUN_PRIVATE_ACL_OK_V1')",
|
|
1109
1113
|
'exit 0',
|
|
1110
1114
|
'} catch {',
|
|
1111
|
-
"[Console]::Error.Write('BLUN_PRIVATE_ACL_ERROR_V1')",
|
|
1115
|
+
"[Console]::Error.Write('BLUN_PRIVATE_ACL_ERROR_V1|' + $_.Exception.GetType().FullName + '|' + $_.Exception.Message)",
|
|
1112
1116
|
'exit 1',
|
|
1113
1117
|
'}',
|
|
1114
1118
|
].join('\n');
|
|
@@ -1127,9 +1131,18 @@ function windowsSystemDirectory(options) {
|
|
|
1127
1131
|
return path.win32.normalize(directory);
|
|
1128
1132
|
}
|
|
1129
1133
|
|
|
1130
|
-
function windowsHostEnvironment(systemDirectory) {
|
|
1134
|
+
function windowsHostEnvironment(systemDirectory, env = process.env) {
|
|
1131
1135
|
const systemRoot = path.win32.dirname(systemDirectory);
|
|
1132
|
-
|
|
1136
|
+
const temporaryDirectory = env.TEMP || env.TMP;
|
|
1137
|
+
if (!temporaryDirectory) {
|
|
1138
|
+
throw new Error('Windows private ACL temporary directory is unavailable.');
|
|
1139
|
+
}
|
|
1140
|
+
return {
|
|
1141
|
+
SystemRoot: systemRoot,
|
|
1142
|
+
WINDIR: systemRoot,
|
|
1143
|
+
TEMP: temporaryDirectory,
|
|
1144
|
+
TMP: env.TMP || temporaryDirectory,
|
|
1145
|
+
};
|
|
1133
1146
|
}
|
|
1134
1147
|
|
|
1135
1148
|
function runWindowsCommand(executable, args, options, env, input) {
|
|
@@ -1190,14 +1203,14 @@ function resolveWindowsPrivateTarget(targetPath) {
|
|
|
1190
1203
|
throw new Error('Invalid Windows private path.');
|
|
1191
1204
|
}
|
|
1192
1205
|
return absoluteTarget;
|
|
1193
|
-
} catch {
|
|
1194
|
-
throw new Error('Windows private
|
|
1206
|
+
} catch (error) {
|
|
1207
|
+
throw new Error('Windows private path validation failed.', { cause: error });
|
|
1195
1208
|
}
|
|
1196
1209
|
}
|
|
1197
1210
|
|
|
1198
1211
|
function windowsAclController(options) {
|
|
1199
1212
|
const systemDirectory = windowsSystemDirectory(options);
|
|
1200
|
-
const hostEnvironment = windowsHostEnvironment(systemDirectory);
|
|
1213
|
+
const hostEnvironment = windowsHostEnvironment(systemDirectory, options.env || process.env);
|
|
1201
1214
|
const whoami = path.win32.join(systemDirectory, 'whoami.exe');
|
|
1202
1215
|
const powershell = path.win32.join(
|
|
1203
1216
|
systemDirectory,
|
|
@@ -1235,7 +1248,7 @@ function windowsAclController(options) {
|
|
|
1235
1248
|
if (!path.isAbsolute(absoluteTarget)
|
|
1236
1249
|
|| root.toLowerCase() === absoluteTarget.toLowerCase()
|
|
1237
1250
|
|| !['directory', 'file', 'file-write', 'tree'].includes(kind)) {
|
|
1238
|
-
throw new Error('Windows private ACL
|
|
1251
|
+
throw new Error('Windows private ACL target validation failed.');
|
|
1239
1252
|
}
|
|
1240
1253
|
const env = {
|
|
1241
1254
|
...hostEnvironment,
|
|
@@ -1253,11 +1266,21 @@ function windowsAclController(options) {
|
|
|
1253
1266
|
env,
|
|
1254
1267
|
input,
|
|
1255
1268
|
);
|
|
1256
|
-
} catch {
|
|
1257
|
-
|
|
1269
|
+
} catch (error) {
|
|
1270
|
+
const stderr = typeof error?.stderr === 'string'
|
|
1271
|
+
? error.stderr : Buffer.isBuffer(error?.stderr) ? error.stderr.toString('utf8') : '';
|
|
1272
|
+
const marker = `${WINDOWS_ACL_ERROR_SENTINEL}|`;
|
|
1273
|
+
const rawDetail = stderr.startsWith(marker) ? stderr.slice(marker.length) : '';
|
|
1274
|
+
const detail = rawDetail.replace(/[\u0000-\u001f\u007f]+/gu, ' ').trim().slice(0, 500);
|
|
1275
|
+
throw new Error(
|
|
1276
|
+
detail.length > 0
|
|
1277
|
+
? `Windows private ACL operation failed for ${kind} target "${absoluteTarget}": ${detail}`
|
|
1278
|
+
: `Windows private ACL operation failed for ${kind} target "${absoluteTarget}".`,
|
|
1279
|
+
{ cause: error },
|
|
1280
|
+
);
|
|
1258
1281
|
}
|
|
1259
1282
|
if (output !== WINDOWS_ACL_OK_SENTINEL) {
|
|
1260
|
-
throw new Error('Windows private ACL
|
|
1283
|
+
throw new Error('Windows private ACL verification returned an unexpected response.');
|
|
1261
1284
|
}
|
|
1262
1285
|
} finally {
|
|
1263
1286
|
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}`;
|
|
@@ -262010,6 +262036,14 @@ function isMediaToolName(name) {
|
|
|
262010
262036
|
"GetMedia"
|
|
262011
262037
|
].includes(name);
|
|
262012
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
|
+
}
|
|
262013
262047
|
function emitMediaProgress(ctx, toolName, phase, data = {}) {
|
|
262014
262048
|
ctx.onUpdate({
|
|
262015
262049
|
kind: "custom",
|
|
@@ -262163,6 +262197,7 @@ var init_blun_media$1 = __esmMin((() => {
|
|
|
262163
262197
|
isError: true,
|
|
262164
262198
|
output: `"${args.path}" is empty.`
|
|
262165
262199
|
};
|
|
262200
|
+
emitMediaProgress(ctx, this.name, "uploading", mediaInlinePreviewPayload(data, fileType.mimeType));
|
|
262166
262201
|
const options = {
|
|
262167
262202
|
signal: ctx.signal,
|
|
262168
262203
|
toolCallId: ctx.toolCallId
|
|
@@ -310934,6 +310969,14 @@ function mediaPayloadNumberArray(payload, keys) {
|
|
|
310934
310969
|
if (samples.length > 0) return samples.slice(0, 512);
|
|
310935
310970
|
}
|
|
310936
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
|
+
}
|
|
310937
310980
|
function mediaProgressFromPayload(payload) {
|
|
310938
310981
|
const nestedProgress = payload["progress"];
|
|
310939
310982
|
const source = typeof nestedProgress === "object" && nestedProgress !== null && !Array.isArray(nestedProgress) ? {
|
|
@@ -310964,7 +311007,8 @@ function mediaProgressFromPayload(payload) {
|
|
|
310964
311007
|
previewMimeType: mediaPayloadText(source, ["preview_mime_type", "previewMimeType", "thumbnail_mime_type", "thumbnailMimeType"]),
|
|
310965
311008
|
sampleUrl: mediaPayloadText(source, ["sample_url", "sampleUrl", "audio_preview_url", "audioPreviewUrl"]),
|
|
310966
311009
|
localPath: mediaPayloadText(source, ["local_path", "localPath"]),
|
|
310967
|
-
waveform: mediaPayloadNumberArray(source, ["waveform", "waveform_samples", "waveformSamples"])
|
|
311010
|
+
waveform: mediaPayloadNumberArray(source, ["waveform", "waveform_samples", "waveformSamples"]),
|
|
311011
|
+
phaseHistory: mediaPayloadTextArray(source, ["phase_history", "phaseHistory"])
|
|
310968
311012
|
};
|
|
310969
311013
|
return Object.fromEntries(Object.entries(progress).filter(([, value]) => value !== void 0));
|
|
310970
311014
|
}
|
|
@@ -401409,6 +401453,21 @@ const BUILTIN_SLASH_COMMAND_DEFINITIONS = [
|
|
|
401409
401453
|
return "idle-only";
|
|
401410
401454
|
}
|
|
401411
401455
|
},
|
|
401456
|
+
{
|
|
401457
|
+
name: "chancenradar",
|
|
401458
|
+
aliases: [],
|
|
401459
|
+
descriptionKey: "command.idea.description",
|
|
401460
|
+
priority: 80,
|
|
401461
|
+
argumentHint: "<objective>",
|
|
401462
|
+
availability: (args) => {
|
|
401463
|
+
const trimmed = args.trim();
|
|
401464
|
+
if (trimmed === "") return "always";
|
|
401465
|
+
const tokens = trimmed.split(/\s+/);
|
|
401466
|
+
const action = tokens[0]?.toLowerCase();
|
|
401467
|
+
if (tokens.length === 1 && (action === "status" || action === "pause" || action === "paused" || action === "resume" || action === "cancel" || action === "clear" || action === "stop")) return "always";
|
|
401468
|
+
return "idle-only";
|
|
401469
|
+
}
|
|
401470
|
+
},
|
|
401412
401471
|
{
|
|
401413
401472
|
name: "loop",
|
|
401414
401473
|
aliases: [],
|
|
@@ -416698,7 +416757,7 @@ function bufferContext(state, chatId, tag) {
|
|
|
416698
416757
|
function channelOrigin(envelope) {
|
|
416699
416758
|
return `Telegram · ${envelope.meta.user ?? envelope.meta.chat_id}`;
|
|
416700
416759
|
}
|
|
416701
|
-
const TELEGRAM_REMOTE_COMMANDS = Object.freeze(["loop", "goal", "idea", "befehle"]);
|
|
416760
|
+
const TELEGRAM_REMOTE_COMMANDS = Object.freeze(["loop", "goal", "idea", "chancenradar", "befehle"]);
|
|
416702
416761
|
function telegramRemoteCommand(text) {
|
|
416703
416762
|
const trimmed = text.trim();
|
|
416704
416763
|
const parsed = parseSlashInput(trimmed);
|
|
@@ -416912,19 +416971,25 @@ function sessionMcpServers() {
|
|
|
416912
416971
|
return telegramChannelMcpServers();
|
|
416913
416972
|
}
|
|
416914
416973
|
function managedTelegramPluginEnabled() {
|
|
416974
|
+
return managedTelegramPluginRoot() !== void 0;
|
|
416975
|
+
}
|
|
416976
|
+
function managedTelegramPluginRoot() {
|
|
416915
416977
|
const blunHome = join(homedir(), ".blun");
|
|
416916
|
-
const managedRoot = join(blunHome, "plugins", "managed", "telegram");
|
|
416917
|
-
if (!existsSync(join(managedRoot, "dist", "mcp-server.mjs"))) return false;
|
|
416918
416978
|
try {
|
|
416919
|
-
|
|
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;
|
|
416920
416983
|
} catch {
|
|
416921
|
-
return
|
|
416984
|
+
return void 0;
|
|
416922
416985
|
}
|
|
416923
416986
|
}
|
|
416924
416987
|
function resolveTelegramMcpEntry() {
|
|
416925
416988
|
const main = process.argv[1];
|
|
416989
|
+
const managedRoot = managedTelegramPluginRoot();
|
|
416926
416990
|
const candidates = [
|
|
416927
416991
|
process.env["BLUN_TELEGRAM_MCP_SERVER"],
|
|
416992
|
+
managedRoot === void 0 ? void 0 : join(managedRoot, "dist", "mcp-server.mjs"),
|
|
416928
416993
|
join(homedir(), ".blun", "plugins", "managed", "telegram", "dist", "mcp-server.mjs"),
|
|
416929
416994
|
main === void 0 ? void 0 : join(dirname(main), "..", "..", "..", "plugins", "telegram", "dist", "mcp-server.mjs")
|
|
416930
416995
|
];
|
|
@@ -417330,8 +417395,10 @@ var TelegramChannelController = class {
|
|
|
417330
417395
|
}
|
|
417331
417396
|
resolveBridgeEntry() {
|
|
417332
417397
|
const main = process.argv[1];
|
|
417398
|
+
const managedRoot = managedTelegramPluginRoot();
|
|
417333
417399
|
const candidates = [
|
|
417334
417400
|
process.env["BLUN_TELEGRAM_BRIDGE"],
|
|
417401
|
+
managedRoot === void 0 ? void 0 : join(managedRoot, "dist", "bridge.mjs"),
|
|
417335
417402
|
join(homedir(), ".blun", "plugins", "managed", "telegram", "dist", "bridge.mjs"),
|
|
417336
417403
|
main === void 0 ? void 0 : join(dirname(main), "..", "..", "..", "plugins", "telegram", "dist", "bridge.mjs")
|
|
417337
417404
|
];
|
|
@@ -420255,6 +420322,42 @@ async function handleIdeaCommand(host, args) {
|
|
|
420255
420322
|
}
|
|
420256
420323
|
});
|
|
420257
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
|
+
}
|
|
420258
420361
|
function parseNextGoalCommand(tokens) {
|
|
420259
420362
|
if (tokens.length === 2 && tokens[1]?.toLowerCase() === "manage") return { kind: "next-manage" };
|
|
420260
420363
|
let index = 1;
|
|
@@ -493272,6 +493375,9 @@ async function handleBuiltInSlashCommand(host, name, args) {
|
|
|
493272
493375
|
case "idea":
|
|
493273
493376
|
await handleIdeaCommand(host, args);
|
|
493274
493377
|
return;
|
|
493378
|
+
case "chancenradar":
|
|
493379
|
+
await handleChanceRadarCommand(host, args);
|
|
493380
|
+
return;
|
|
493275
493381
|
case "loop":
|
|
493276
493382
|
await handleLoopCommand(host, args);
|
|
493277
493383
|
return;
|
|
@@ -498562,6 +498668,10 @@ var MediaActivityStore = class {
|
|
|
498562
498668
|
const mediaKind = event.mediaKind === "media" && previous?.mediaKind !== void 0 ? previous.mediaKind : event.mediaKind ?? previous?.mediaKind ?? "media";
|
|
498563
498669
|
const phase = mediaActivityStatusKey(event.phase ?? previous?.phase ?? "processing");
|
|
498564
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
|
+
}
|
|
498565
498675
|
if (phaseHistory.at(-1) !== phase) phaseHistory.push(phase);
|
|
498566
498676
|
this.jobs.set(key, {
|
|
498567
498677
|
...previous,
|
|
@@ -513379,7 +513489,6 @@ var BlunTUI = class {
|
|
|
513379
513489
|
this.showStatus(warning, "warning");
|
|
513380
513490
|
}
|
|
513381
513491
|
async init() {
|
|
513382
|
-
if (this.startupWorkspaceSelectionPending) return false;
|
|
513383
513492
|
await this.measureStartupPhase("experimental_ms", async () => {
|
|
513384
513493
|
setExperimentalFeatures(await this.harness.getExperimentalFeatures());
|
|
513385
513494
|
});
|
|
@@ -513392,6 +513501,7 @@ var BlunTUI = class {
|
|
|
513392
513501
|
} else this.authFlow.enterManagedAccountUnavailableStartupState(accountContext.kind);
|
|
513393
513502
|
return false;
|
|
513394
513503
|
}
|
|
513504
|
+
if (this.startupWorkspaceSelectionPending) return false;
|
|
513395
513505
|
await this.measureStartupPhase("provider_models_ms", () => this.refreshProviderModelsBeforeSession());
|
|
513396
513506
|
const { startup } = this.options;
|
|
513397
513507
|
const { workDir } = this.state.appState;
|
|
@@ -514089,7 +514199,7 @@ var BlunTUI = class {
|
|
|
514089
514199
|
};
|
|
514090
514200
|
this.telegramRemoteCommandContext = context;
|
|
514091
514201
|
const execution = item.telegramCommandName === "befehle" ? Promise.resolve().then(() => {
|
|
514092
|
-
context.responses.push("/loop\n/goal\n/idea\n/befehle");
|
|
514202
|
+
context.responses.push("/loop\n/goal\n/idea\n/chancenradar\n/befehle");
|
|
514093
514203
|
}) : dispatchInput(this, item.text);
|
|
514094
514204
|
execution.catch((error) => {
|
|
514095
514205
|
this.showError(formatErrorMessage$2(error));
|