blun-king-cli 9.1.527 → 9.1.550

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.
Files changed (108) hide show
  1. package/LIESMICH.txt +13 -834
  2. package/README.md +41 -799
  3. package/bin/agent-resume-snapshot.cjs +31 -0
  4. package/bin/assistant-message-offload-policy.cjs +23 -2
  5. package/bin/codebase-search-runtime.cjs +23 -0
  6. package/bin/context-performance-policy.cjs +2 -5
  7. package/bin/context-pressure-policy.cjs +20 -0
  8. package/bin/cron-run-output.cjs +45 -0
  9. package/bin/cron-run-store.cjs +145 -0
  10. package/bin/durable-task-resume-policy.cjs +130 -0
  11. package/bin/durable-task-resume-runtime.cjs +117 -0
  12. package/bin/durable-task-resume-store.cjs +88 -0
  13. package/bin/editable-tool-approval-policy.cjs +540 -0
  14. package/bin/editable-tool-approval-runtime.cjs +99 -0
  15. package/bin/empty-response-retry-policy.cjs +29 -0
  16. package/bin/history-offload-pressure-policy.cjs +33 -0
  17. package/bin/html-to-research-markdown.cjs +146 -0
  18. package/bin/programmatic-context-isolation.cjs +25 -0
  19. package/bin/programmatic-tool-runtime.mjs +627 -0
  20. package/bin/read-continuation-policy.cjs +36 -5
  21. package/bin/scoped-cron-run-policy.cjs +358 -0
  22. package/bin/skill-activation-performance-policy.cjs +9 -0
  23. package/bin/startup-preferences.cjs +3 -3
  24. package/bin/structured-agent-swarm-output.cjs +325 -0
  25. package/bin/structured-subagent-output.cjs +252 -0
  26. package/bin/subagent-context-fork-policy.cjs +155 -0
  27. package/bin/subagent-skill-policy.cjs +204 -0
  28. package/bin/telegram-approval-relay.cjs +2 -1
  29. package/bin/telegram-direct-focus-policy.cjs +25 -1
  30. package/bin/todo-list-turn-policy.cjs +111 -1
  31. package/bin/tool-result-offload-policy.cjs +43 -1
  32. package/bin/turn-thinking-policy.cjs +6 -15
  33. package/bin/turn-tool-performance-policy.cjs +5 -4
  34. package/bin/update-notice.js +14 -18
  35. package/bin/user-message-offload-policy.cjs +12 -1
  36. package/blun.mjs +1406 -265
  37. package/codebase-index/README.md +82 -0
  38. package/codebase-index/codebase_index.py +469 -0
  39. package/package.json +24 -38
  40. package/telegram-plugin/bin/telegram-typing-keepalive.cjs +89 -0
  41. package/telegram-plugin/dist/bridge.mjs +8 -1
  42. package/CHANGELOG.md +0 -260
  43. package/agent-spine-plugin/CHANGELOG.md +0 -406
  44. package/agent-spine-plugin/CONTRIBUTING.md +0 -52
  45. package/agent-spine-plugin/README.md +0 -344
  46. package/agent-spine-plugin/SECURITY.md +0 -47
  47. package/agent-spine-plugin/docs/acceptance.md +0 -61
  48. package/agent-spine-plugin/docs/architecture.md +0 -183
  49. package/agent-spine-plugin/docs/attention.md +0 -121
  50. package/agent-spine-plugin/docs/automatic-continuity.md +0 -79
  51. package/agent-spine-plugin/docs/channel-runtime.md +0 -92
  52. package/agent-spine-plugin/docs/coordination.md +0 -138
  53. package/agent-spine-plugin/docs/feed-transport.md +0 -99
  54. package/agent-spine-plugin/docs/gateway-runtime.md +0 -116
  55. package/agent-spine-plugin/docs/harness-reference.md +0 -45
  56. package/agent-spine-plugin/docs/host-integration.md +0 -129
  57. package/agent-spine-plugin/docs/https-transport.md +0 -116
  58. package/agent-spine-plugin/docs/learning.md +0 -133
  59. package/agent-spine-plugin/docs/object-transport.md +0 -93
  60. package/agent-spine-plugin/docs/peer-transport.md +0 -88
  61. package/agent-spine-plugin/docs/preflight-recall.md +0 -69
  62. package/agent-spine-plugin/docs/preservation-contract.md +0 -53
  63. package/agent-spine-plugin/docs/quality-gates.md +0 -50
  64. package/agent-spine-plugin/docs/relationships.md +0 -73
  65. package/agent-spine-plugin/docs/releasing.md +0 -83
  66. package/agent-spine-plugin/docs/roadmap.md +0 -307
  67. package/agent-spine-plugin/docs/selfstarter.md +0 -88
  68. package/agent-spine-plugin/docs/session-briefing.md +0 -74
  69. package/agent-spine-plugin/docs/shared-memory.md +0 -259
  70. package/agent-spine-plugin/docs/source-roots.md +0 -86
  71. package/agent-spine-plugin/docs/sqlite-transport.md +0 -76
  72. package/agent-spine-plugin/scripts/check-hosts.js +0 -195
  73. package/agent-spine-plugin/scripts/check-install.js +0 -569
  74. package/agent-spine-plugin/scripts/check-syntax.js +0 -29
  75. package/agent-spine-plugin/scripts/github-actions.js +0 -11
  76. package/agent-spine-plugin/scripts/release-check.js +0 -128
  77. package/agent-spine-plugin/scripts/run-acceptance.js +0 -19
  78. package/agent-spine-plugin/scripts/run-checks.js +0 -46
  79. package/agent-spine-plugin/scripts/run-tests-hermetic.js +0 -73
  80. package/agent-spine-plugin/spine-example/1-identity.md +0 -12
  81. package/agent-spine-plugin/spine-example/2-voice.md +0 -6
  82. package/agent-spine-plugin/spine-example/3-conduct.md +0 -8
  83. package/agent-spine-plugin/spine-example/4-history.md +0 -4
  84. package/bin/package-regression-policy.cjs +0 -77
  85. package/release-planned-removals.json +0 -15
  86. package/scripts/check-active-profile-plugin-startup.js +0 -36
  87. package/scripts/check-approval-observability-regression.js +0 -111
  88. package/scripts/check-approval-queue-shortcuts-regression.js +0 -65
  89. package/scripts/check-bundled-agent-spine-regression.js +0 -48
  90. package/scripts/check-copy-command-regression.js +0 -74
  91. package/scripts/check-historical-tool-result-preview-regression.js +0 -77
  92. package/scripts/check-mcp-startup-wait-budget.js +0 -48
  93. package/scripts/check-package-regression.js +0 -38
  94. package/scripts/check-plugin-startup-regression.js +0 -53
  95. package/scripts/check-queue-controls-regression.js +0 -189
  96. package/scripts/check-release-metadata.js +0 -103
  97. package/scripts/check-reload-agent-spine-regression.js +0 -76
  98. package/scripts/check-resume-replay-regression.js +0 -100
  99. package/scripts/check-session-cancel-regression.js +0 -43
  100. package/scripts/check-session-picker-resume-metrics-regression.js +0 -97
  101. package/scripts/check-session-start-hook-context-regression.js +0 -228
  102. package/scripts/check-shell-terminal-isolation-regression.js +0 -81
  103. package/scripts/check-slash-escape-regression.js +0 -89
  104. package/scripts/check-telegram-bridge-watchdog.js +0 -60
  105. package/scripts/check-telegram-loop-exactly-once-regression.js +0 -71
  106. package/scripts/check-todo-loop-regression.js +0 -78
  107. package/scripts/check-todo-recovery-catalog-regression.js +0 -50
  108. /package/{scripts → bin}/fix-node-pty-perms.js +0 -0
@@ -1,74 +0,0 @@
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');
@@ -1,77 +0,0 @@
1
- #!/usr/bin/env node
2
- 'use strict';
3
-
4
- const fs = require('node:fs');
5
- const path = require('node:path');
6
-
7
- const bundlePath = process.env.BLUN_BUNDLE_UNDER_TEST
8
- ? path.resolve(process.env.BLUN_BUNDLE_UNDER_TEST)
9
- : path.resolve(__dirname, '..', 'blun.mjs');
10
- const bundle = fs.readFileSync(bundlePath, 'utf8');
11
-
12
- function assert(condition, message) {
13
- if (!condition) throw new Error(message);
14
- }
15
-
16
- const rendererMatch = bundle.match(
17
- /function renderHistoricalToolResultReference\(toolName, toolCallId, text, outputPath, storageMode\) \{[\s\S]*?\n\}/,
18
- );
19
- assert(rendererMatch, 'HISTORICAL_TOOL_RESULT_PREVIEW_REGRESSION: renderer is missing');
20
- const policy = require('../bin/tool-result-offload-policy.cjs');
21
-
22
- const renderHistoricalToolResultReference = Function(
23
- 'Buffer',
24
- 'TOOL_RESULT_OFFLOAD_MARKER',
25
- 'TOOL_RESULT_RECOVERY_PAGE_LINES',
26
- 'TOOL_RESULT_HISTORICAL_PREVIEW_CHARS',
27
- 'readContinuationLineOffset',
28
- 'createToolResultPreview',
29
- `return (${rendererMatch[0]});`,
30
- )(
31
- Buffer,
32
- '[Tool result offloaded]',
33
- 20,
34
- policy.TOOL_RESULT_HISTORICAL_PREVIEW_CHARS,
35
- () => undefined,
36
- (text, maxChars) => `${text.slice(0, maxChars / 2)}\n[preview omitted]\n${text.slice(-maxChars / 2)}`,
37
- );
38
-
39
- const text = `HEAD-${'a'.repeat(900)}-TAIL`;
40
- const rendered = renderHistoricalToolResultReference(
41
- 'Bash',
42
- 'call-preview',
43
- text,
44
- 'C:/archive/call-preview.txt',
45
- 'private_archive',
46
- );
47
-
48
- assert(
49
- rendered.includes('[preview: head and tail]'),
50
- 'HISTORICAL_TOOL_RESULT_PREVIEW_REGRESSION: historical offload has no preview marker',
51
- );
52
- assert(
53
- rendered.includes('HEAD-') && rendered.includes('-TAIL'),
54
- 'HISTORICAL_TOOL_RESULT_PREVIEW_REGRESSION: historical offload does not preserve both ends',
55
- );
56
- assert(
57
- rendered.includes('output_path: C:/archive/call-preview.txt'),
58
- 'HISTORICAL_TOOL_RESULT_PREVIEW_REGRESSION: recovery path was lost',
59
- );
60
- const compacted = policy.compactPersistedToolResultReference([
61
- { type: 'text', text: rendered },
62
- ]);
63
- const compactedText = compacted[0].text;
64
- assert(
65
- compactedText.includes('[preview: head and tail]'),
66
- 'HISTORICAL_TOOL_RESULT_PREVIEW_REGRESSION: context compaction removed the preview',
67
- );
68
- assert(
69
- compactedText.includes('HEAD-') && compactedText.includes('-TAIL'),
70
- 'HISTORICAL_TOOL_RESULT_PREVIEW_REGRESSION: compact preview lost an endpoint',
71
- );
72
- assert(
73
- compactedText.length < rendered.length,
74
- 'HISTORICAL_TOOL_RESULT_PREVIEW_REGRESSION: compact preview is not bounded',
75
- );
76
-
77
- process.stdout.write('historical-tool-result-preview-regression PASS\n');
@@ -1,48 +0,0 @@
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 bundlePath = path.resolve(__dirname, '..', 'blun.mjs');
9
- const source = fs.readFileSync(bundlePath, 'utf8');
10
- const match = source.match(
11
- /async function blunWaitForInitialMcpBudget\(mcp, signal\) \{[\s\S]*?\n\}/u,
12
- );
13
- assert.ok(match, 'the bundle must contain the bounded initial MCP wait helper');
14
- assert.match(
15
- source,
16
- /await blunWaitForInitialMcpBudget\(this\.agent\.mcp, signal\)/u,
17
- 'tool-capable turns must use the bounded initial MCP wait helper',
18
- );
19
-
20
- const context = vm.createContext({ setTimeout });
21
- vm.runInContext(`${match[0]}; globalThis.waitForInitialMcp = blunWaitForInitialMcpBudget;`, context);
22
-
23
- let initialLoadFinished = false;
24
- const mcp = {
25
- waitForInitialLoad() {
26
- return new Promise((resolve) => {
27
- const timer = setTimeout(() => {
28
- initialLoadFinished = true;
29
- resolve();
30
- }, 20_000);
31
- timer.unref();
32
- });
33
- },
34
- };
35
-
36
- (async () => {
37
- const startedAt = performance.now();
38
- await context.waitForInitialMcp(mcp, undefined);
39
- const elapsedMs = performance.now() - startedAt;
40
-
41
- assert.ok(elapsedMs >= 2_300, `wait budget returned too early: ${elapsedMs} ms`);
42
- assert.ok(elapsedMs < 3_200, `slow optional MCP blocked the turn: ${elapsedMs} ms`);
43
- assert.equal(initialLoadFinished, false, 'the slow MCP must continue loading in the background');
44
- process.stdout.write(`MCP startup wait regression: PASS (${Math.round(elapsedMs)} ms)\n`);
45
- })().catch((error) => {
46
- process.stderr.write(`${error.stack || error}\n`);
47
- process.exitCode = 1;
48
- });
@@ -1,38 +0,0 @@
1
- #!/usr/bin/env node
2
- 'use strict';
3
-
4
- const fs = require('node:fs');
5
- const crypto = require('node:crypto');
6
- const path = require('node:path');
7
- const {
8
- comparePackagePaths,
9
- listTarGzPaths,
10
- } = require('../bin/package-regression-policy.cjs');
11
-
12
- const [, , previousTarball, currentTarball, allowlistPath = path.join(__dirname, '..', 'release-planned-removals.json')] = process.argv;
13
- if (!previousTarball || !currentTarball) {
14
- console.error('Usage: node scripts/check-package-regression.js <previous.tgz> <current.tgz> [planned-removals.json]');
15
- process.exit(2);
16
- }
17
-
18
- const planned = JSON.parse(fs.readFileSync(allowlistPath, 'utf8'));
19
- const comparison = comparePackagePaths(
20
- listTarGzPaths(previousTarball),
21
- listTarGzPaths(currentTarball),
22
- planned,
23
- );
24
- const sha256 = (filePath) => crypto.createHash('sha256').update(fs.readFileSync(filePath)).digest('hex');
25
- const result = {
26
- ok: comparison.unexpectedRemovals.length === 0,
27
- previous: { path: path.resolve(previousTarball), sha256: sha256(previousTarball) },
28
- current: { path: path.resolve(currentTarball), sha256: sha256(currentTarball) },
29
- checkedStablePaths: comparison.checkedStablePaths,
30
- currentStablePaths: comparison.currentStablePaths,
31
- removedCount: comparison.unexpectedRemovals.length,
32
- unexpectedRemovals: comparison.unexpectedRemovals,
33
- };
34
- if (!result.ok) {
35
- console.error(JSON.stringify(result, null, 2));
36
- process.exit(1);
37
- }
38
- console.log(JSON.stringify(result, null, 2));
@@ -1,53 +0,0 @@
1
- 'use strict';
2
-
3
- const assert = require('node:assert/strict');
4
- const fs = require('node:fs');
5
- const os = require('node:os');
6
- const path = require('node:path');
7
-
8
- const { installManagedAgentSpinePlugin } = require('../bin/plugin-bootstrap');
9
-
10
- const root = fs.mkdtempSync(path.join(os.tmpdir(), 'blun-plugin-startup-'));
11
- const packageRoot = path.join(root, 'package');
12
- const sourceRoot = path.join(packageRoot, 'agent-spine-plugin');
13
- const profileA = path.join(root, 'profiles', 'a');
14
- const profileB = path.join(root, 'profiles', 'b');
15
-
16
- fs.mkdirSync(path.join(sourceRoot, 'src'), { recursive: true });
17
- fs.writeFileSync(path.join(sourceRoot, 'package.json'), '{"name":"agent-spine"}\n');
18
- fs.writeFileSync(path.join(sourceRoot, 'src', 'mcp.js'), 'export const ready = true;\n');
19
-
20
- const originalReadFileSync = fs.readFileSync;
21
- let sourceReads = 0;
22
- fs.readFileSync = function countedReadFileSync(filePath, ...args) {
23
- const absolutePath = path.resolve(String(filePath));
24
- if (absolutePath === sourceRoot || absolutePath.startsWith(`${sourceRoot}${path.sep}`)) {
25
- sourceReads += 1;
26
- }
27
- return originalReadFileSync.call(this, filePath, ...args);
28
- };
29
-
30
- try {
31
- const first = installManagedAgentSpinePlugin({ packageRoot, blunDir: profileA });
32
- installManagedAgentSpinePlugin({ packageRoot, blunDir: profileA });
33
- installManagedAgentSpinePlugin({ packageRoot, blunDir: profileB });
34
- installManagedAgentSpinePlugin({ packageRoot, blunDir: profileB });
35
-
36
- assert.equal(
37
- sourceReads,
38
- 2,
39
- 'one launcher process must read each managed source file only once',
40
- );
41
-
42
- fs.writeFileSync(path.join(first.pluginRoot, 'src', 'mcp.js'), 'tampered\n');
43
- assert.throws(
44
- () => installManagedAgentSpinePlugin({ packageRoot, blunDir: profileA }),
45
- /digest mismatch/,
46
- 'source caching must not weaken installed-tree verification',
47
- );
48
-
49
- process.stdout.write('Plugin startup regression gate: PASS\n');
50
- } finally {
51
- fs.readFileSync = originalReadFileSync;
52
- fs.rmSync(root, { recursive: true, force: true });
53
- }
@@ -1,189 +0,0 @@
1
- const assert = require('node:assert/strict');
2
- const fs = require('node:fs');
3
- const path = require('node:path');
4
- const { enqueueTelegramAddressed } = require('../bin/telegram-addressed-priority.cjs');
5
- const { enqueueTelegramBotPriority } = require('../bin/telegram-bot-priority.cjs');
6
- const { enqueueTelegramDirect } = require('../bin/telegram-direct-focus-policy.cjs');
7
- const { enqueueTelegramUrgent } = require('../bin/telegram-urgent-policy.cjs');
8
-
9
- const bundle = fs.readFileSync(path.join(__dirname, '..', 'blun.mjs'), 'utf8');
10
-
11
- function between(start, end) {
12
- const startIndex = bundle.indexOf(start);
13
- assert.notEqual(startIndex, -1, `missing bundle marker: ${start}`);
14
- const endIndex = bundle.indexOf(end, startIndex + start.length);
15
- assert.notEqual(endIndex, -1, `missing bundle marker: ${end}`);
16
- return bundle.slice(startIndex, endIndex);
17
- }
18
-
19
- function method(name, nextName) {
20
- const source = between(`\n\t${name}(`, `\n\t${nextName}(`).trim();
21
- return Function(`"use strict"; return ({${source}}).${name};`)();
22
- }
23
-
24
- const ctrlS = between('\n\t\teditor.onCtrlS = () => {', '\n\t\teditor.onCtrlB =');
25
- assert.match(ctrlS, /host\.flushQueuedMessages\(/);
26
- assert.doesNotMatch(ctrlS, /toggleTodoPanelExpansion|shortcut_todo_expand/);
27
-
28
- const escape = between('\n\t\teditor.onEscape =', '\n\t\teditor.onShiftTab =');
29
- const releaseIndex = escape.indexOf('host.releaseNextQueuedMessage()');
30
- const cancelIndex = escape.indexOf('host.markCurrentRunCancelledByUser()');
31
- assert.ok(releaseIndex >= 0, 'Escape must release queued work');
32
- assert.ok(cancelIndex > releaseIndex, 'Escape must release queued work before cancelling the run');
33
-
34
- const releaseNextQueuedMessage = method('releaseNextQueuedMessage', 'markCurrentRunCancelledByUser');
35
- const releaseHost = {
36
- queueSteerInFlight: undefined,
37
- queueFlushBatchRemaining: 0,
38
- state: {
39
- queuedMessages: [{ text: 'one' }, { text: 'two' }, { text: 'three' }],
40
- ui: { requestRender() {} },
41
- },
42
- steerQueueFlushPrefixCalls: 0,
43
- updateQueueDisplayCalls: 0,
44
- steerQueueFlushPrefix() { this.steerQueueFlushPrefixCalls += 1; },
45
- updateQueueDisplay() { this.updateQueueDisplayCalls += 1; },
46
- };
47
-
48
- assert.equal(releaseNextQueuedMessage.call(releaseHost), true);
49
- assert.equal(releaseHost.queueFlushBatchRemaining, 1);
50
- assert.equal(releaseNextQueuedMessage.call(releaseHost), true);
51
- assert.equal(releaseHost.queueFlushBatchRemaining, 2);
52
- assert.equal(releaseNextQueuedMessage.call(releaseHost), true);
53
- assert.equal(releaseHost.queueFlushBatchRemaining, 3);
54
- assert.equal(releaseNextQueuedMessage.call(releaseHost), false);
55
- assert.equal(releaseHost.queueFlushBatchRemaining, 3);
56
- assert.equal(releaseHost.steerQueueFlushPrefixCalls, 3);
57
-
58
- const flushQueuedMessages = method('flushQueuedMessages', 'steerQueueFlushPrefix');
59
- const flushHost = {
60
- queueSteerInFlight: { items: [{ text: 'already accepted' }] },
61
- queueFlushBatchRemaining: 0,
62
- state: {
63
- queuedMessages: [{ text: 'one' }, { text: 'two' }, { text: 'three' }],
64
- editor: { setText() {}, inputMode: 'prompt' },
65
- },
66
- steerQueueFlushPrefixCalls: 0,
67
- scheduleQueueDrainCalls: 0,
68
- steerQueueFlushPrefix() { this.steerQueueFlushPrefixCalls += 1; },
69
- scheduleQueueDrain() { this.scheduleQueueDrainCalls += 1; },
70
- };
71
-
72
- flushQueuedMessages.call(flushHost, '', 'prompt');
73
- assert.equal(flushHost.queueFlushBatchRemaining, 4);
74
- assert.equal(flushHost.steerQueueFlushPrefixCalls, 1);
75
- assert.equal(flushHost.scheduleQueueDrainCalls, 1);
76
-
77
- const commitQueuedSteer = method('commitQueuedSteer', 'recoverRejectedActiveSteer');
78
- let commits = 0;
79
- const inFlight = {
80
- items: [{ text: 'telegram message' }],
81
- confirmationTimer: undefined,
82
- onCommit() { commits += 1; },
83
- };
84
- const commitHost = {
85
- queueSteerInFlight: inFlight,
86
- updateQueueDisplay() {},
87
- state: { ui: { requestRender() {} } },
88
- steerQueueFlushPrefix() {},
89
- scheduleQueueDrain() {},
90
- };
91
-
92
- commitQueuedSteer.call(commitHost, inFlight);
93
- commitQueuedSteer.call(commitHost, inFlight);
94
- assert.equal(commits, 1);
95
- assert.equal(commitHost.queueSteerInFlight, undefined);
96
-
97
- const acceptQueuedSteer = method('acceptQueuedSteer', 'commitQueuedSteerIfReady');
98
- let acceptedCommits = 0;
99
- const acceptedInFlight = {
100
- items: [{ channelDeliveryTrace: { messageId: '63508' } }],
101
- accepted: false,
102
- stepStarted: false,
103
- turnEnded: false,
104
- onCommit() { acceptedCommits += 1; },
105
- };
106
- const acceptedHost = {
107
- queueSteerInFlight: acceptedInFlight,
108
- traced: [],
109
- traceTelegramDelivery(event) { this.traced.push(event); },
110
- commitQueuedSteer(item) {
111
- assert.equal(item, acceptedInFlight);
112
- if (this.queueSteerInFlight !== item) return;
113
- this.queueSteerInFlight = undefined;
114
- item.onCommit();
115
- },
116
- };
117
-
118
- assert.equal(acceptQueuedSteer.call(acceptedHost, acceptedInFlight, {
119
- accepted: true,
120
- duplicate: false,
121
- turnId: 77,
122
- }, true), true);
123
- assert.equal(acceptedCommits, 1, 'durably accepted steer must commit before a delayed step event');
124
- assert.equal(acceptedHost.queueSteerInFlight, undefined);
125
- assert.equal(acceptedHost.traced.length, 1);
126
- assert.equal(acceptedHost.traced[0].stage, 'accepted');
127
-
128
- const recoverRejectedActiveSteer = method('recoverRejectedActiveSteer', 'quarantineQueuedSteer');
129
- let restored = 0;
130
- const rejectedInFlight = {
131
- items: [{ text: 'keep me queued', channelDeliveryFailures: 2 }],
132
- onRestore() { restored += 1; },
133
- };
134
- const rejectedHost = {
135
- queueSteerInFlight: rejectedInFlight,
136
- state: { queuedMessages: [], ui: { requestRender() {} } },
137
- traced: [],
138
- traceTelegramDelivery(event) { this.traced.push(event); },
139
- updateQueueDisplay() {},
140
- streamingUI: { hasActiveTurn() { return true; } },
141
- };
142
-
143
- assert.equal(recoverRejectedActiveSteer.call(rejectedHost, rejectedInFlight), false);
144
- assert.equal(restored, 1);
145
- assert.equal(rejectedHost.state.queuedMessages.length, 1, 'rejected steer must remain queued');
146
- assert.equal(rejectedHost.state.queuedMessages[0].text, 'keep me queued');
147
- assert.equal('channelDeliveryFailures' in rejectedHost.state.queuedMessages[0], false);
148
-
149
- const channelDelivery = between('\n\tasync deliverQueuedChannelHead() {', '\n\tinstallChannelReplyGuard(');
150
- assert.match(
151
- channelDelivery,
152
- /if \(!result\.accepted\) return this\.recoverRejectedActiveSteer\(inFlight\);[\s\S]*return this\.acceptQueuedSteer\(inFlight, result, true\);/,
153
- 'channel delivery must commit a durably accepted active steer',
154
- );
155
- assert.doesNotMatch(
156
- channelDelivery,
157
- /channel steer confirmation timed out|CHANNEL_STEER_CONFIRM_TIMEOUT_MS/,
158
- 'durably accepted channel messages must not be restored by a wall-clock timeout',
159
- );
160
-
161
- const ctrlSDelivery = between('\n\tsteerQueueFlushPrefix() {', '\n\tdrainOneQueuedMessage() {');
162
- assert.match(
163
- ctrlSDelivery,
164
- /if \(!result\.accepted\) this\.recoverRejectedActiveSteer\(inFlight\);[\s\S]*else this\.acceptQueuedSteer\(inFlight, result\);/,
165
- 'Ctrl+S delivery must use the same durable acceptance rule',
166
- );
167
-
168
- const priorityQueue = [{ id: 'normal-a' }, { id: 'normal-b' }];
169
- enqueueTelegramBotPriority(priorityQueue, { id: 'bot-a', channelBotPriority: true });
170
- enqueueTelegramBotPriority(priorityQueue, { id: 'bot-b', channelBotPriority: true });
171
- enqueueTelegramAddressed(priorityQueue, { id: 'addressed-a', channelAddressed: true });
172
- enqueueTelegramAddressed(priorityQueue, { id: 'addressed-b', channelAddressed: true });
173
- enqueueTelegramDirect(priorityQueue, { id: 'direct-a', channelDirect: true });
174
- enqueueTelegramDirect(priorityQueue, { id: 'direct-b', channelDirect: true });
175
- enqueueTelegramUrgent(priorityQueue, { id: 'urgent-a', channelUrgent: true });
176
- enqueueTelegramUrgent(priorityQueue, { id: 'urgent-b', channelUrgent: true });
177
- assert.deepEqual(
178
- priorityQueue.map((item) => item.id),
179
- [
180
- 'urgent-a', 'urgent-b',
181
- 'direct-a', 'direct-b',
182
- 'addressed-a', 'addressed-b',
183
- 'bot-a', 'bot-b',
184
- 'normal-a', 'normal-b',
185
- ],
186
- 'Telegram queue priority must be urgent, direct, addressed, bot, normal with stable FIFO inside each class',
187
- );
188
-
189
- console.log('queue controls regression: PASS');
@@ -1,103 +0,0 @@
1
- 'use strict';
2
-
3
- const fs = require('node:fs');
4
- const path = require('node:path');
5
-
6
- const DEFAULT_MANIFEST_URL = 'https://chat.blun.ai/blun-code-version.json';
7
- const DEFAULT_DOCS_URL = 'https://docs.blun.ai/changelog.html';
8
- const DEFAULT_REGISTRY_URL = 'https://registry.npmjs.org';
9
- const MAX_RESPONSE_BYTES = 2 * 1024 * 1024;
10
-
11
- function assert(condition, message) {
12
- if (!condition) throw new Error(message);
13
- }
14
-
15
- function readUtf8(filePath) {
16
- return fs.readFileSync(filePath, 'utf8');
17
- }
18
-
19
- function firstChangelogVersion(changelog) {
20
- return /^##\s+(\d+\.\d+\.\d+)\b/mu.exec(changelog)?.[1] || null;
21
- }
22
-
23
- function checkLocalReleaseMetadata(root = path.resolve(__dirname, '..')) {
24
- const packageJson = JSON.parse(readUtf8(path.join(root, 'package.json')));
25
- const version = String(packageJson.version || '');
26
- const packageName = String(packageJson.name || '');
27
- assert(/^\d+\.\d+\.\d+$/u.test(version), 'RELEASE_VERSION_INVALID');
28
- assert(packageName.length > 0, 'RELEASE_PACKAGE_NAME_MISSING');
29
-
30
- const changelogVersion = firstChangelogVersion(readUtf8(path.join(root, 'CHANGELOG.md')));
31
- assert(changelogVersion === version, `CHANGELOG_VERSION_MISMATCH:${changelogVersion || 'missing'}!=${version}`);
32
-
33
- const installNeedle = `${packageName}@${version}`;
34
- for (const fileName of ['README.md', 'LIESMICH.txt']) {
35
- assert(readUtf8(path.join(root, fileName)).includes(installNeedle), `INSTALL_VERSION_MISMATCH:${fileName}`);
36
- }
37
- return { packageName, version };
38
- }
39
-
40
- async function fetchLimited(url, kind) {
41
- const response = await fetch(url, {
42
- headers: { accept: kind === 'json' ? 'application/json' : 'text/html' },
43
- redirect: 'follow',
44
- signal: AbortSignal.timeout(15_000),
45
- });
46
- assert(response.ok, `RELEASE_FETCH_FAILED:${response.status}:${url}`);
47
- const declaredLength = Number(response.headers.get('content-length'));
48
- assert(!Number.isFinite(declaredLength) || declaredLength <= MAX_RESPONSE_BYTES, `RELEASE_RESPONSE_TOO_LARGE:${url}`);
49
- const body = await response.text();
50
- assert(Buffer.byteLength(body, 'utf8') <= MAX_RESPONSE_BYTES, `RELEASE_RESPONSE_TOO_LARGE:${url}`);
51
- return kind === 'json' ? JSON.parse(body) : body;
52
- }
53
-
54
- async function checkExternalReleaseMetadata(local, options = {}) {
55
- const cacheBust = `cb=${Date.now()}`;
56
- const registryUrl = options.registryUrl || DEFAULT_REGISTRY_URL;
57
- const manifestUrl = `${options.manifestUrl || DEFAULT_MANIFEST_URL}?${cacheBust}`;
58
- const docsUrl = `${options.docsUrl || DEFAULT_DOCS_URL}?${cacheBust}`;
59
- const encodedName = encodeURIComponent(local.packageName);
60
- const [tags, metadata, manifest, docs] = await Promise.all([
61
- fetchLimited(`${registryUrl}/-/package/${encodedName}/dist-tags`, 'json'),
62
- fetchLimited(`${registryUrl}/${encodedName}/${local.version}`, 'json'),
63
- fetchLimited(manifestUrl, 'json'),
64
- fetchLimited(docsUrl, 'html'),
65
- ]);
66
-
67
- assert(tags.latest === local.version, `NPM_LATEST_MISMATCH:${tags.latest || 'missing'}!=${local.version}`);
68
- assert(metadata.version === local.version, `NPM_VERSION_MISMATCH:${metadata.version || 'missing'}!=${local.version}`);
69
- assert(manifest.name === local.packageName, `MANIFEST_PACKAGE_MISMATCH:${manifest.name || 'missing'}!=${local.packageName}`);
70
- assert(manifest.latest === local.version, `MANIFEST_VERSION_MISMATCH:${manifest.latest || 'missing'}!=${local.version}`);
71
- assert(typeof metadata.dist?.integrity === 'string' && metadata.dist.integrity.length > 0, 'NPM_INTEGRITY_MISSING');
72
- assert(manifest.integrity === metadata.dist.integrity, 'MANIFEST_INTEGRITY_MISMATCH');
73
- assert(docs.includes(`<title>BLUN King ${local.version} `), `PUBLIC_CHANGELOG_VERSION_MISMATCH:${local.version}`);
74
- return { ...local, integrity: metadata.dist.integrity };
75
- }
76
-
77
- function parseArgs(argv) {
78
- const external = argv.includes('--external');
79
- const rootArg = argv.find((arg) => arg.startsWith('--root='));
80
- return { external, root: rootArg ? path.resolve(rootArg.slice('--root='.length)) : undefined };
81
- }
82
-
83
- async function main(argv = process.argv.slice(2)) {
84
- const options = parseArgs(argv);
85
- const local = checkLocalReleaseMetadata(options.root);
86
- const result = options.external ? await checkExternalReleaseMetadata(local) : local;
87
- process.stdout.write(`release-metadata PASS ${JSON.stringify(result)}\n`);
88
- return result;
89
- }
90
-
91
- if (require.main === module) {
92
- main().catch((error) => {
93
- process.stderr.write(`release-metadata BLOCK ${error.message}\n`);
94
- process.exitCode = 1;
95
- });
96
- }
97
-
98
- module.exports = {
99
- checkExternalReleaseMetadata,
100
- checkLocalReleaseMetadata,
101
- firstChangelogVersion,
102
- main,
103
- };
@@ -1,76 +0,0 @@
1
- 'use strict';
2
-
3
- const assert = require('node:assert/strict');
4
- const fs = require('node:fs');
5
- const os = require('node:os');
6
- const path = require('node:path');
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
- const reloadStart = bundle.indexOf('async reloadSession(input) {');
12
- const bootstrapCall = bundle.indexOf('installBundledAgentSpineForReload({ blunDir: this.homeDir });', reloadStart);
13
- const pluginReload = bundle.indexOf('await this.reloadPlugins({});', reloadStart);
14
-
15
- assert.notEqual(reloadStart, -1, 'reloadSession must exist');
16
- assert.ok(
17
- bootstrapCall > reloadStart && bootstrapCall < pluginReload,
18
- '/reload must materialize bundled AgentSpine before reloading the plugin catalogue',
19
- );
20
-
21
- const helperPath = path.join(packageRoot, 'bin', 'reload-plugin-bootstrap.cjs');
22
- assert.ok(fs.existsSync(helperPath), 'reload plugin bootstrap helper must be packaged');
23
- const { installBundledAgentSpineForReload } = require(helperPath);
24
-
25
- const root = fs.mkdtempSync(path.join(os.tmpdir(), 'blun-reload-agent-spine-'));
26
- const profile = path.join(root, 'profile');
27
- const pluginsDir = path.join(profile, 'plugins');
28
- const installedFile = path.join(pluginsDir, 'installed.json');
29
- const unrelatedRoot = path.join(root, 'unrelated-plugin');
30
- fs.mkdirSync(pluginsDir, { recursive: true });
31
- fs.mkdirSync(unrelatedRoot, { recursive: true });
32
- fs.writeFileSync(installedFile, `${JSON.stringify({
33
- version: 1,
34
- plugins: [{
35
- id: 'unrelated',
36
- root: unrelatedRoot,
37
- source: 'local-path',
38
- enabled: false,
39
- capabilities: {},
40
- }],
41
- }, null, 2)}\n`);
42
-
43
- try {
44
- const first = installBundledAgentSpineForReload({
45
- packageRoot,
46
- blunDir: profile,
47
- now: '2026-09-01T00:00:00.000Z',
48
- });
49
- const installed = JSON.parse(fs.readFileSync(installedFile, 'utf8'));
50
- const agentSpine = installed.plugins.find((plugin) => plugin.id === 'agent-spine');
51
- const unrelated = installed.plugins.find((plugin) => plugin.id === 'unrelated');
52
-
53
- assert.ok(agentSpine, 'reload bootstrap must add bundled AgentSpine');
54
- assert.equal(agentSpine.root, first.pluginRoot);
55
- assert.equal(agentSpine.enabled, true);
56
- assert.equal(agentSpine.capabilities.mcpServers['agent-spine'].enabled, true);
57
- assert.equal(unrelated.root, unrelatedRoot, 'reload bootstrap must preserve unrelated plugins');
58
- assert.equal(unrelated.enabled, false, 'reload bootstrap must preserve unrelated plugin state');
59
- assert.equal(
60
- fs.readFileSync(path.join(agentSpine.root, 'package.json'), 'utf8'),
61
- fs.readFileSync(path.join(packageRoot, 'agent-spine-plugin', 'package.json'), 'utf8'),
62
- 'managed AgentSpine must come from the current package bundle',
63
- );
64
-
65
- const beforeSecondRun = fs.readFileSync(installedFile, 'utf8');
66
- const second = installBundledAgentSpineForReload({
67
- packageRoot,
68
- blunDir: profile,
69
- now: '2026-09-01T00:01:00.000Z',
70
- });
71
- assert.equal(second.reused, true, 'an unchanged bundle must be reused');
72
- assert.equal(fs.readFileSync(installedFile, 'utf8'), beforeSecondRun, 'idempotent reload must not rewrite installed.json');
73
- process.stdout.write('Reload AgentSpine regression gate: PASS\n');
74
- } finally {
75
- fs.rmSync(root, { recursive: true, force: true });
76
- }