blun-king-cli 9.1.526 → 9.1.527
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -0
- package/LIESMICH.txt +1 -1
- package/README.md +1 -1
- package/blun.mjs +53 -0
- package/package.json +3 -2
- package/scripts/check-copy-command-regression.js +74 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 9.1.527 - 2026-09-01
|
|
4
|
+
|
|
5
|
+
- Adds DeepAgents' `/copy` workflow to copy the latest completed, non-empty assistant response through BLUN's existing cross-platform clipboard path.
|
|
6
|
+
- Keeps `/copy` available during active turns, where the existing command queue runs it after the response completes, and reports empty-history or clipboard failures in all six interface languages.
|
|
7
|
+
- Adds a red-to-green regression for command registration, dispatch, response selection, byte-preserving clipboard content, and released UI text.
|
|
8
|
+
|
|
3
9
|
## 9.1.526 - 2026-09-01
|
|
4
10
|
|
|
5
11
|
- Materializes the AgentSpine version bundled with the current package before `/reload` rebuilds the plugin catalogue and MCP configuration.
|
package/LIESMICH.txt
CHANGED
package/README.md
CHANGED
package/blun.mjs
CHANGED
|
@@ -403290,6 +403290,10 @@ registerUiCatalogFragment({
|
|
|
403290
403290
|
"command.telegramAccess.description": "Manage the Telegram bot token and allowed chats",
|
|
403291
403291
|
"command.add-dir.description": "Add or list an additional workspace directory",
|
|
403292
403292
|
"command.experiments.description": "Manage experimental features",
|
|
403293
|
+
"command.copy.description": "Copy the latest assistant response to the clipboard.",
|
|
403294
|
+
"copy.status.copied": "Copied the latest response to the clipboard.",
|
|
403295
|
+
"copy.status.none": "There is no response to copy yet.",
|
|
403296
|
+
"copy.error.failed": "Could not copy the latest response: {error}",
|
|
403293
403297
|
"command.reload.description": "Reload the session and apply runtime and interface settings",
|
|
403294
403298
|
"command.reload-tui.description": "Reload interface settings only",
|
|
403295
403299
|
"command.compact.description": "Compact the conversation context",
|
|
@@ -403349,6 +403353,10 @@ registerUiCatalogFragment({
|
|
|
403349
403353
|
"command.telegramAccess.description": "Telegram-Bot-Token und freigegebene Chats verwalten",
|
|
403350
403354
|
"command.add-dir.description": "Zusätzlichen Arbeitsbereichsordner hinzufügen oder vorhandene auflisten",
|
|
403351
403355
|
"command.experiments.description": "Experimentelle Funktionen verwalten",
|
|
403356
|
+
"command.copy.description": "Neueste Assistentenantwort in die Zwischenablage übernehmen.",
|
|
403357
|
+
"copy.status.copied": "Die neueste Antwort wurde in die Zwischenablage übernommen.",
|
|
403358
|
+
"copy.status.none": "Es ist noch keine Antwort zum Kopieren verfügbar.",
|
|
403359
|
+
"copy.error.failed": "Die neueste Antwort konnte nicht kopiert werden: {error}",
|
|
403352
403360
|
"command.reload.description": "Sitzung neu laden und Runtime- sowie Oberflächeneinstellungen anwenden",
|
|
403353
403361
|
"command.reload-tui.description": "Nur Oberflächeneinstellungen neu laden",
|
|
403354
403362
|
"command.compact.description": "Konversationskontext komprimieren",
|
|
@@ -403408,6 +403416,10 @@ registerUiCatalogFragment({
|
|
|
403408
403416
|
"command.telegramAccess.description": "Gestionar el token del bot de Telegram y los chats permitidos",
|
|
403409
403417
|
"command.add-dir.description": "Añadir o listar directorios adicionales del espacio de trabajo",
|
|
403410
403418
|
"command.experiments.description": "Gestionar funciones experimentales",
|
|
403419
|
+
"command.copy.description": "Copiar la respuesta más reciente del asistente al portapapeles.",
|
|
403420
|
+
"copy.status.copied": "La respuesta más reciente se copió al portapapeles.",
|
|
403421
|
+
"copy.status.none": "Todavía no hay ninguna respuesta que copiar.",
|
|
403422
|
+
"copy.error.failed": "No se pudo copiar la respuesta más reciente: {error}",
|
|
403411
403423
|
"command.reload.description": "Recargar la sesión y aplicar los ajustes del entorno de ejecución y de la interfaz",
|
|
403412
403424
|
"command.reload-tui.description": "Recargar solo los ajustes de la interfaz",
|
|
403413
403425
|
"command.compact.description": "Compactar el contexto de la conversación",
|
|
@@ -403467,6 +403479,10 @@ registerUiCatalogFragment({
|
|
|
403467
403479
|
"command.telegramAccess.description": "Gérer le jeton du bot Telegram et les discussions autorisées",
|
|
403468
403480
|
"command.add-dir.description": "Ajouter ou répertorier un répertoire d’espace de travail supplémentaire",
|
|
403469
403481
|
"command.experiments.description": "Gérer les fonctions expérimentales",
|
|
403482
|
+
"command.copy.description": "Copier la réponse la plus récente de l’assistant dans le presse-papiers.",
|
|
403483
|
+
"copy.status.copied": "La réponse la plus récente a été copiée dans le presse-papiers.",
|
|
403484
|
+
"copy.status.none": "Aucune réponse à copier pour le moment.",
|
|
403485
|
+
"copy.error.failed": "Impossible de copier la réponse la plus récente : {error}",
|
|
403470
403486
|
"command.reload.description": "Recharger la session et appliquer les paramètres d’exécution et d’interface",
|
|
403471
403487
|
"command.reload-tui.description": "Recharger uniquement les paramètres de l’interface",
|
|
403472
403488
|
"command.compact.description": "Compacter le contexte de la conversation",
|
|
@@ -403526,6 +403542,10 @@ registerUiCatalogFragment({
|
|
|
403526
403542
|
"command.telegramAccess.description": "Hantera Telegram-botens API-token och tillåtna chattar",
|
|
403527
403543
|
"command.add-dir.description": "Lägg till eller lista en extra katalog för arbetsytan",
|
|
403528
403544
|
"command.experiments.description": "Hantera experimentella funktioner",
|
|
403545
|
+
"command.copy.description": "Kopiera assistentens senaste svar till urklipp.",
|
|
403546
|
+
"copy.status.copied": "Det senaste svaret kopierades till urklipp.",
|
|
403547
|
+
"copy.status.none": "Det finns inget svar att kopiera ännu.",
|
|
403548
|
+
"copy.error.failed": "Det gick inte att kopiera det senaste svaret: {error}",
|
|
403529
403549
|
"command.reload.description": "Läs in sessionen på nytt och tillämpa körningsmiljö- och gränssnittsinställningar",
|
|
403530
403550
|
"command.reload-tui.description": "Läs bara in gränssnittsinställningarna på nytt",
|
|
403531
403551
|
"command.compact.description": "Komprimera konversationskontexten",
|
|
@@ -403585,6 +403605,10 @@ registerUiCatalogFragment({
|
|
|
403585
403605
|
"command.telegramAccess.description": "Spravovat token telegramového bota a povolené chaty",
|
|
403586
403606
|
"command.add-dir.description": "Přidat nebo vypsat další adresář pracovního prostoru",
|
|
403587
403607
|
"command.experiments.description": "Spravovat experimentální funkce",
|
|
403608
|
+
"command.copy.description": "Zkopírovat nejnovější odpověď asistenta do schránky.",
|
|
403609
|
+
"copy.status.copied": "Nejnovější odpověď byla zkopírována do schránky.",
|
|
403610
|
+
"copy.status.none": "Zatím není k dispozici žádná odpověď ke zkopírování.",
|
|
403611
|
+
"copy.error.failed": "Nejnovější odpověď se nepodařilo zkopírovat: {error}",
|
|
403588
403612
|
"command.reload.description": "Znovu načíst relaci a použít nastavení běhového prostředí a rozhraní",
|
|
403589
403613
|
"command.reload-tui.description": "Znovu načíst pouze nastavení rozhraní",
|
|
403590
403614
|
"command.compact.description": "Komprimovat kontext konverzace",
|
|
@@ -403976,6 +404000,13 @@ const BUILTIN_SLASH_COMMAND_DEFINITIONS = [
|
|
|
403976
404000
|
priority: 60,
|
|
403977
404001
|
availability: "idle-only"
|
|
403978
404002
|
},
|
|
404003
|
+
{
|
|
404004
|
+
name: "copy",
|
|
404005
|
+
aliases: [],
|
|
404006
|
+
descriptionKey: "command.copy.description",
|
|
404007
|
+
priority: 40,
|
|
404008
|
+
availability: "always"
|
|
404009
|
+
},
|
|
403979
404010
|
{
|
|
403980
404011
|
name: "reload",
|
|
403981
404012
|
aliases: [],
|
|
@@ -496816,6 +496847,25 @@ async function handlePremortemCommand(host, args) {
|
|
|
496816
496847
|
}
|
|
496817
496848
|
host.sendMessage(host.requireSession(), plan, { parts: buildPremortemPrompt(plan) });
|
|
496818
496849
|
}
|
|
496850
|
+
function findLatestAssistantResponse(entries) {
|
|
496851
|
+
for (let index = entries.length - 1; index >= 0; index--) {
|
|
496852
|
+
const entry = entries[index];
|
|
496853
|
+
if (entry?.kind === "assistant" && typeof entry.content === "string" && entry.content.trim().length > 0) return entry.content;
|
|
496854
|
+
}
|
|
496855
|
+
}
|
|
496856
|
+
async function handleCopyCommand(host) {
|
|
496857
|
+
const text = findLatestAssistantResponse(host.state.transcriptEntries);
|
|
496858
|
+
if (text === void 0) {
|
|
496859
|
+
host.showStatus(uiText("copy.status.none"));
|
|
496860
|
+
return;
|
|
496861
|
+
}
|
|
496862
|
+
try {
|
|
496863
|
+
await copyTextToClipboard(text);
|
|
496864
|
+
host.showStatus(uiText("copy.status.copied"));
|
|
496865
|
+
} catch (error) {
|
|
496866
|
+
host.showError(uiText("copy.error.failed", { error: formatErrorMessage$2(error) }));
|
|
496867
|
+
}
|
|
496868
|
+
}
|
|
496819
496869
|
async function handleBuiltInSlashCommand(host, name, args) {
|
|
496820
496870
|
switch (name) {
|
|
496821
496871
|
case "exit":
|
|
@@ -496858,6 +496908,9 @@ async function handleBuiltInSlashCommand(host, name, args) {
|
|
|
496858
496908
|
case "experiments":
|
|
496859
496909
|
await showExperimentsPanel(host);
|
|
496860
496910
|
return;
|
|
496911
|
+
case "copy":
|
|
496912
|
+
await handleCopyCommand(host);
|
|
496913
|
+
return;
|
|
496861
496914
|
case "reload":
|
|
496862
496915
|
await handleReloadCommand(host);
|
|
496863
496916
|
return;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "blun-king-cli",
|
|
3
|
-
"version": "9.1.
|
|
3
|
+
"version": "9.1.527",
|
|
4
4
|
"description": "BLUN CLI - your own AI agent with a Telegram channel. Get it done. With BLUN.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"bin": {
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
},
|
|
10
10
|
"scripts": {
|
|
11
11
|
"test": "node --test test/*.test.js",
|
|
12
|
-
"prepack": "node scripts/check-release-metadata.js && node scripts/check-shell-terminal-isolation-regression.js && node scripts/check-todo-loop-regression.js && node scripts/check-telegram-loop-exactly-once-regression.js && node scripts/check-session-picker-resume-metrics-regression.js && node scripts/check-bundled-agent-spine-regression.js && node scripts/check-reload-agent-spine-regression.js && node scripts/check-todo-recovery-catalog-regression.js && node scripts/check-historical-tool-result-preview-regression.js && node scripts/check-session-start-hook-context-regression.js && node scripts/check-queue-controls-regression.js && node scripts/check-approval-queue-shortcuts-regression.js && node scripts/check-approval-observability-regression.js && node scripts/check-slash-escape-regression.js && node scripts/check-telegram-bridge-watchdog.js && node scripts/check-resume-replay-regression.js && node scripts/check-session-cancel-regression.js && node scripts/check-plugin-startup-regression.js && node scripts/check-active-profile-plugin-startup.js && node scripts/check-mcp-startup-wait-budget.js",
|
|
12
|
+
"prepack": "node scripts/check-release-metadata.js && node scripts/check-copy-command-regression.js && node scripts/check-shell-terminal-isolation-regression.js && node scripts/check-todo-loop-regression.js && node scripts/check-telegram-loop-exactly-once-regression.js && node scripts/check-session-picker-resume-metrics-regression.js && node scripts/check-bundled-agent-spine-regression.js && node scripts/check-reload-agent-spine-regression.js && node scripts/check-todo-recovery-catalog-regression.js && node scripts/check-historical-tool-result-preview-regression.js && node scripts/check-session-start-hook-context-regression.js && node scripts/check-queue-controls-regression.js && node scripts/check-approval-queue-shortcuts-regression.js && node scripts/check-approval-observability-regression.js && node scripts/check-slash-escape-regression.js && node scripts/check-telegram-bridge-watchdog.js && node scripts/check-resume-replay-regression.js && node scripts/check-session-cancel-regression.js && node scripts/check-plugin-startup-regression.js && node scripts/check-active-profile-plugin-startup.js && node scripts/check-mcp-startup-wait-budget.js",
|
|
13
13
|
"release:verify": "node scripts/check-release-metadata.js --external",
|
|
14
14
|
"postinstall": "node scripts/fix-node-pty-perms.js"
|
|
15
15
|
},
|
|
@@ -36,6 +36,7 @@
|
|
|
36
36
|
"native/",
|
|
37
37
|
"release-planned-removals.json",
|
|
38
38
|
"scripts/check-package-regression.js",
|
|
39
|
+
"scripts/check-copy-command-regression.js",
|
|
39
40
|
"scripts/check-active-profile-plugin-startup.js",
|
|
40
41
|
"scripts/check-approval-queue-shortcuts-regression.js",
|
|
41
42
|
"scripts/check-bundled-agent-spine-regression.js",
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const assert = require('node:assert/strict');
|
|
4
|
+
const fs = require('node:fs');
|
|
5
|
+
const path = require('node:path');
|
|
6
|
+
const vm = require('node:vm');
|
|
7
|
+
|
|
8
|
+
const rootArg = process.argv.find((value) => value.startsWith('--root='));
|
|
9
|
+
const packageRoot = rootArg ? path.resolve(rootArg.slice('--root='.length)) : path.resolve(__dirname, '..');
|
|
10
|
+
const bundle = fs.readFileSync(path.join(packageRoot, 'blun.mjs'), 'utf8');
|
|
11
|
+
|
|
12
|
+
function extractFunction(name) {
|
|
13
|
+
const start = bundle.indexOf(`function ${name}(`);
|
|
14
|
+
assert.notEqual(start, -1, `${name} must exist`);
|
|
15
|
+
const open = bundle.indexOf('{', start);
|
|
16
|
+
let depth = 0;
|
|
17
|
+
for (let index = open; index < bundle.length; index += 1) {
|
|
18
|
+
if (bundle[index] === '{') depth += 1;
|
|
19
|
+
if (bundle[index] === '}') depth -= 1;
|
|
20
|
+
if (depth === 0) return bundle.slice(start, index + 1);
|
|
21
|
+
}
|
|
22
|
+
assert.fail(`${name} must have a complete function body`);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
assert.match(
|
|
26
|
+
bundle,
|
|
27
|
+
/name:\s*"copy"[\s\S]{0,180}descriptionKey:\s*"command\.copy\.description"[\s\S]{0,180}availability:\s*"always"/,
|
|
28
|
+
'/copy must be registered and available while a turn is running',
|
|
29
|
+
);
|
|
30
|
+
assert.match(bundle, /case "copy":\s*await handleCopyCommand\(host\);\s*return;/, '/copy must dispatch to its handler');
|
|
31
|
+
assert.match(bundle, /await copyTextToClipboard\(text\);/, '/copy must use the existing cross-platform clipboard helper');
|
|
32
|
+
|
|
33
|
+
const selectorSource = extractFunction('findLatestAssistantResponse');
|
|
34
|
+
const findLatestAssistantResponse = vm.runInNewContext(`(${selectorSource})`);
|
|
35
|
+
assert.equal(
|
|
36
|
+
findLatestAssistantResponse([
|
|
37
|
+
{ kind: 'assistant', content: ' previous answer ' },
|
|
38
|
+
{ kind: 'user', content: 'question' },
|
|
39
|
+
{ kind: 'assistant', content: ' ' },
|
|
40
|
+
]),
|
|
41
|
+
' previous answer ',
|
|
42
|
+
'the selector must ignore user and empty assistant entries while preserving response bytes',
|
|
43
|
+
);
|
|
44
|
+
assert.equal(findLatestAssistantResponse([{ kind: 'user', content: 'question' }]), undefined);
|
|
45
|
+
|
|
46
|
+
const requiredUiStrings = [
|
|
47
|
+
'Copy the latest assistant response to the clipboard.',
|
|
48
|
+
'Copied the latest response to the clipboard.',
|
|
49
|
+
'There is no response to copy yet.',
|
|
50
|
+
'Could not copy the latest response: {error}',
|
|
51
|
+
'Neueste Assistentenantwort in die Zwischenablage übernehmen.',
|
|
52
|
+
'Die neueste Antwort wurde in die Zwischenablage übernommen.',
|
|
53
|
+
'Es ist noch keine Antwort zum Kopieren verfügbar.',
|
|
54
|
+
'Die neueste Antwort konnte nicht kopiert werden: {error}',
|
|
55
|
+
'Copiar la respuesta más reciente del asistente al portapapeles.',
|
|
56
|
+
'La respuesta más reciente se copió al portapapeles.',
|
|
57
|
+
'Todavía no hay ninguna respuesta que copiar.',
|
|
58
|
+
'No se pudo copiar la respuesta más reciente: {error}',
|
|
59
|
+
'Copier la réponse la plus récente de l’assistant dans le presse-papiers.',
|
|
60
|
+
'La réponse la plus récente a été copiée dans le presse-papiers.',
|
|
61
|
+
'Aucune réponse à copier pour le moment.',
|
|
62
|
+
'Impossible de copier la réponse la plus récente : {error}',
|
|
63
|
+
'Kopiera assistentens senaste svar till urklipp.',
|
|
64
|
+
'Det senaste svaret kopierades till urklipp.',
|
|
65
|
+
'Det finns inget svar att kopiera ännu.',
|
|
66
|
+
'Det gick inte att kopiera det senaste svaret: {error}',
|
|
67
|
+
'Zkopírovat nejnovější odpověď asistenta do schránky.',
|
|
68
|
+
'Nejnovější odpověď byla zkopírována do schránky.',
|
|
69
|
+
'Zatím není k dispozici žádná odpověď ke zkopírování.',
|
|
70
|
+
'Nejnovější odpověď se nepodařilo zkopírovat: {error}',
|
|
71
|
+
];
|
|
72
|
+
for (const text of requiredUiStrings) assert.ok(bundle.includes(text), `missing released UI text: ${text}`);
|
|
73
|
+
|
|
74
|
+
process.stdout.write('Copy command regression gate: PASS\n');
|