blun-king-cli 9.1.624 → 9.1.625

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 (142) hide show
  1. package/CHANGELOG.md +62 -213
  2. package/LIESMICH.txt +57 -74
  3. package/README.md +76 -119
  4. package/agent-spine-plugin/scripts/check-install.js +4 -4
  5. package/agent-spine-plugin/src/hook.js +0 -4
  6. package/agent-spine-plugin/src/lib/documents.js +1 -1
  7. package/agent-spine-plugin/src/lib/hook-context.js +0 -1
  8. package/agent-spine-plugin/src/lib/host-instruction-budget.js +2 -1
  9. package/agent-spine-plugin/src/lib/preflight.js +9 -38
  10. package/agent-spine-plugin/src/lib/source-roots.js +1 -1
  11. package/bin/agentspine-king-goal-inbox.mjs +127 -127
  12. package/bin/agentspine-king-goal-intake.mjs +106 -106
  13. package/bin/agentspine-king-host-runner.mjs +109 -109
  14. package/bin/agentspine-king-snapshot-policy.cjs +80 -80
  15. package/bin/agentspine-king-status-policy.mjs +226 -226
  16. package/bin/agentspine-king-worker-host.mjs +160 -160
  17. package/bin/core-bootstrap.js +71 -100
  18. package/bin/launcher-mode.js +93 -93
  19. package/bin/launcher-restart-policy.cjs +131 -131
  20. package/bin/launcher-runtime.js +89 -254
  21. package/bin/managed-node.js +186 -186
  22. package/bin/managed-plugin-selection.cjs +109 -116
  23. package/bin/native-module-repair.js +201 -201
  24. package/bin/node-runtime.js +34 -34
  25. package/bin/node-version.js +49 -49
  26. package/bin/plugin-bootstrap.js +149 -172
  27. package/bin/private-paths.js +1578 -1624
  28. package/bin/runtime-exit-ledger.cjs +144 -144
  29. package/bin/runtime-exit-ledger.d.cts +23 -23
  30. package/bin/standard-tools-bootstrap.js +277 -272
  31. package/bin/turn-thinking-policy.cjs +82 -82
  32. package/bin/update-copy.js +200 -200
  33. package/bin/update-lease.js +597 -599
  34. package/bin/update-notice.js +1568 -1568
  35. package/bin/verify-agent-behavior.cjs +122 -122
  36. package/bin/verify-agent-components.cjs +104 -104
  37. package/bin/verify-bundled-agent-sources.cjs +91 -96
  38. package/bin/windows-node-crash-dump.cjs +289 -289
  39. package/blun.mjs +187640 -180475
  40. package/bundled-agent-sources.json +26 -30
  41. package/codebase-index/codebase_index.py +462 -470
  42. package/dist-web/assets/{CodeBlockNode-TKKkJX8L.js → CodeBlockNode-BMbuG85x.js} +1 -1
  43. package/dist-web/assets/{CodeBlockShell.vue_vue_type_style_index_0_lang-CUj2Ds5x.js → CodeBlockShell.vue_vue_type_style_index_0_lang-C1fv7l_Z.js} +1 -1
  44. package/dist-web/assets/DesignSystemView-6HgAUEmC.css +1 -0
  45. package/dist-web/assets/DesignSystemView-CEEBqXSX.js +12 -0
  46. package/dist-web/assets/{Tooltip-CTWv7WCU.js → Tooltip--YoauJ24.js} +1 -1
  47. package/dist-web/assets/{abnfDiagram-N423BO3Z-DA8V8Zjh.js → abnfDiagram-N423BO3Z-DZvBWkRg.js} +1 -1
  48. package/dist-web/assets/{arc-YtddKbdY.js → arc-B04JATeX.js} +1 -1
  49. package/dist-web/assets/{architectureDiagram-T3A2C74G-CGEiObmo.js → architectureDiagram-T3A2C74G-DmYnMep2.js} +1 -1
  50. package/dist-web/assets/{blockDiagram-VBNYF7ZC-Bd5Vt50e.js → blockDiagram-VBNYF7ZC-DJ1gPS1C.js} +1 -1
  51. package/dist-web/assets/{c4Diagram-5PPSVZJV-D2ApPM54.js → c4Diagram-5PPSVZJV-DmjxvUbd.js} +1 -1
  52. package/dist-web/assets/channel-472fcEvw.js +1 -0
  53. package/dist-web/assets/{chunk-2GRJ4B5K-BDcIEZSl.js → chunk-2GRJ4B5K-AB0raj3I.js} +1 -1
  54. package/dist-web/assets/{chunk-2Q5K7J3B-6aSabCX7.js → chunk-2Q5K7J3B-Drf039vM.js} +1 -1
  55. package/dist-web/assets/{chunk-5RXB4S5H-BFRQvIJS.js → chunk-5RXB4S5H-D5ILttmz.js} +1 -1
  56. package/dist-web/assets/{chunk-5VM5RSS4-ChAa-DNY.js → chunk-5VM5RSS4-DrLSdi8y.js} +1 -1
  57. package/dist-web/assets/{chunk-6Q2QTUOP-BIfrb2Jc.js → chunk-6Q2QTUOP-LiaqdEOQ.js} +1 -1
  58. package/dist-web/assets/{chunk-GF5L2VYU-BR0gvtWI.js → chunk-GF5L2VYU-AIYkb--o.js} +1 -1
  59. package/dist-web/assets/{chunk-JWPE2WC7-BgXKsx5E.js → chunk-JWPE2WC7-CogdNl0i.js} +1 -1
  60. package/dist-web/assets/{chunk-KBJHAD2P-I8mrZhpz.js → chunk-KBJHAD2P-DHjpNtZv.js} +1 -1
  61. package/dist-web/assets/{chunk-RYQCIY6F-BMPaem92.js → chunk-RYQCIY6F-BUSOqH2X.js} +1 -1
  62. package/dist-web/assets/{chunk-XXDRQBXY-COy0J-0j.js → chunk-XXDRQBXY-BpoMV0bh.js} +1 -1
  63. package/dist-web/assets/classDiagram-JCYQIIEL-BTZeLngj.js +1 -0
  64. package/dist-web/assets/classDiagram-v2-OCEON4UE-BTZeLngj.js +1 -0
  65. package/dist-web/assets/{cose-bilkent-JH36ORCC-MosTTRdo.js → cose-bilkent-JH36ORCC-BJr2I61o.js} +1 -1
  66. package/dist-web/assets/{cynefin-VYW2F7L2-5tjPgrkP.js → cynefin-VYW2F7L2-HtysQZ4A.js} +1 -1
  67. package/dist-web/assets/{cynefinDiagram-MW4NZA55-CbDcdInR.js → cynefinDiagram-MW4NZA55-CcpC_2uv.js} +1 -1
  68. package/dist-web/assets/{dagre-VZM6K2ZE-YaBom1ZH.js → dagre-VZM6K2ZE-D8Ao0LUg.js} +1 -1
  69. package/dist-web/assets/{diagram-7IWD3JNH-Da4m-Hgz.js → diagram-7IWD3JNH-BITiLhjX.js} +1 -1
  70. package/dist-web/assets/{diagram-B4RE2ZJO-CAUUegxg.js → diagram-B4RE2ZJO-CJtwfy9f.js} +1 -1
  71. package/dist-web/assets/{diagram-LBJQPF4R-o21mWKG4.js → diagram-LBJQPF4R-BsVTh6w8.js} +1 -1
  72. package/dist-web/assets/{diagram-Q27KOJAE-BA3z3sXf.js → diagram-Q27KOJAE-pA_MUCUf.js} +1 -1
  73. package/dist-web/assets/{diagram-UB23O5K3-CGRdSSDK.js → diagram-UB23O5K3-CIY8z7vu.js} +1 -1
  74. package/dist-web/assets/{ebnfDiagram-BXEA7PRR-Bw-iU9FP.js → ebnfDiagram-BXEA7PRR-C3FMioqp.js} +1 -1
  75. package/dist-web/assets/{erDiagram-JOGREHBK-BwM1yiR0.js → erDiagram-JOGREHBK-CzoyKmKc.js} +1 -1
  76. package/dist-web/assets/{flowDiagram-UKHOOZJN-z05eOR1Q.js → flowDiagram-UKHOOZJN-DOqfushy.js} +1 -1
  77. package/dist-web/assets/{ganttDiagram-PKOTCBZU-BMUubNTl.js → ganttDiagram-PKOTCBZU-Cf9XQSWl.js} +1 -1
  78. package/dist-web/assets/{gitGraphDiagram-DS77QQ5N-Dccbi1wU.js → gitGraphDiagram-DS77QQ5N-DHUDofE5.js} +1 -1
  79. package/dist-web/assets/{index-h0KEOmkU.js → index-B6qEMQMx.js} +14 -14
  80. package/dist-web/assets/{index-C9eDH3u-.css → index-BmKj7CB1.css} +1 -1
  81. package/dist-web/assets/{index-BBbC5LRF.js → index-DokxDX1w.js} +1 -1
  82. package/dist-web/assets/{index-D48p0M2S.js → index-auojtiAA.js} +2 -2
  83. package/dist-web/assets/{index10-Qbuw1N6u.js → index10-Drb2n5e5.js} +1 -1
  84. package/dist-web/assets/{index11-g8v025wN.js → index11-Cx-aV4G2.js} +1 -1
  85. package/dist-web/assets/{index5-BYNcV8BY.js → index5-Dg3qGTyH.js} +1 -1
  86. package/dist-web/assets/{index6-BetANtnV.js → index6-BgvtIICP.js} +1 -1
  87. package/dist-web/assets/{index7-BcPbgCKN.js → index7-uEnniPQG.js} +1 -1
  88. package/dist-web/assets/{index8-B1lp-RvF.js → index8-h4IKc0ek.js} +2 -2
  89. package/dist-web/assets/{infoDiagram-6WML65LV-BF_pbUnN.js → infoDiagram-6WML65LV-D-3tvEiJ.js} +1 -1
  90. package/dist-web/assets/{ishikawaDiagram-WSZJBQD7-Dedv4urS.js → ishikawaDiagram-WSZJBQD7-DIIBNv7a.js} +1 -1
  91. package/dist-web/assets/{journeyDiagram-NVQOT4AX-B1Ex3b6o.js → journeyDiagram-NVQOT4AX-DP4r1Vlx.js} +1 -1
  92. package/dist-web/assets/{kanban-definition-27J2QSJJ-BtCGliBH.js → kanban-definition-27J2QSJJ-C3_Ztgxv.js} +1 -1
  93. package/dist-web/assets/{linear-ByItc8Qr.js → linear-DnqhIw-y.js} +1 -1
  94. package/dist-web/assets/{mermaid.core-B1M2O7nN.js → mermaid.core-xYUgBa3T.js} +4 -4
  95. package/dist-web/assets/{mindmap-definition-FAOFIHXS-D0UHKUyj.js → mindmap-definition-FAOFIHXS-l-dq-zfr.js} +1 -1
  96. package/dist-web/assets/{pegDiagram-VL7TDLO6-D7zYCVmG.js → pegDiagram-VL7TDLO6-C8vXQiaJ.js} +1 -1
  97. package/dist-web/assets/{pieDiagram-7S7Q4E2Y-Bd0i9CHa.js → pieDiagram-7S7Q4E2Y-CyNRSsI6.js} +1 -1
  98. package/dist-web/assets/{quadrantDiagram-CIZ2JOQS-MG-Ug47d.js → quadrantDiagram-CIZ2JOQS-D-w8AD_g.js} +1 -1
  99. package/dist-web/assets/{railroadDiagram-AXF67PYL-AZdl90Gj.js → railroadDiagram-AXF67PYL-BLkyQCWy.js} +1 -1
  100. package/dist-web/assets/{requirementDiagram-LRYGKXZP-DjplF9kt.js → requirementDiagram-LRYGKXZP-CEWO7XfT.js} +1 -1
  101. package/dist-web/assets/{sankeyDiagram-W5VNT64P-Bi9Zgfjs.js → sankeyDiagram-W5VNT64P-ByGoAaGj.js} +1 -1
  102. package/dist-web/assets/{sequenceDiagram-SI44F4Z6-ChEpdCvb.js → sequenceDiagram-SI44F4Z6-Bhn9GQtd.js} +1 -1
  103. package/dist-web/assets/{sizeCapture-X5ZJPWSS-B5RFUdaa.js → sizeCapture-X5ZJPWSS-jVKR9CTu.js} +1 -1
  104. package/dist-web/assets/{stateDiagram-OKZ733FA-BkRblSKp.js → stateDiagram-OKZ733FA-wW3XbegA.js} +1 -1
  105. package/dist-web/assets/stateDiagram-v2-UEYNNEHI-B8aids-Z.js +1 -0
  106. package/dist-web/assets/{swimlanes-SLNWSIFB-DLXH6Zhc.js → swimlanes-SLNWSIFB-B8QyFS7G.js} +2 -2
  107. package/dist-web/assets/swimlanesDiagram-ULZ7WXOC-B8XAkdSm.js +8 -0
  108. package/dist-web/assets/{timeline-definition-Z64GVDOM-C_N88siO.js → timeline-definition-Z64GVDOM-DpvxS-1A.js} +1 -1
  109. package/dist-web/assets/{vennDiagram-T6HMQDX7-DMsnvDt0.js → vennDiagram-T6HMQDX7-BzGM0ihY.js} +1 -1
  110. package/dist-web/assets/{vue.runtime.esm-bundler-B-3uIy85.js → vue.runtime.esm-bundler-BOcpwIX1.js} +1 -1
  111. package/dist-web/assets/{wardleyDiagram-T6FBY63Y-C9SJIPzq.js → wardleyDiagram-T6FBY63Y-2DKAOs5e.js} +1 -1
  112. package/dist-web/assets/{xychartDiagram-ELKLHX3M-DZLWA6Go.js → xychartDiagram-ELKLHX3M-B0SP0TXd.js} +1 -1
  113. package/dist-web/index.html +32 -32
  114. package/dist-web/vis/index.html.gz +0 -0
  115. package/package.json +1 -1
  116. package/scripts/fix-node-pty-perms.js +53 -53
  117. package/standard-skills/blun-app-design-system/SKILL.md +30 -30
  118. package/standard-skills/blun-app-design-system/provenance.json +17 -17
  119. package/standard-skills/blun-app-design-system/references/tokens.md +48 -48
  120. package/standard-skills/blun-app-design-system/scripts/check-blun-design.mjs +49 -49
  121. package/standard-skills/translate-native/README.md +1 -1
  122. package/standard-skills/translate-native/scripts/blun_language_guard.py +2 -0
  123. package/standard-skills/translate-native/scripts/language_gateway.py +2 -0
  124. package/standard-skills/translate-native/scripts/pre_output_guard.py +2 -0
  125. package/standard-tools/language-guard/blun_language_guard.py +2 -0
  126. package/standard-tools/language-guard/language_gateway.py +2 -0
  127. package/standard-tools/language-guard/pre_output_guard.py +2 -0
  128. package/telegram-plugin/blun.plugin.json +20 -20
  129. package/telegram-plugin/commands/access.md +37 -37
  130. package/telegram-plugin/dist/bridge.mjs +310 -187
  131. package/telegram-plugin/dist/mcp-server.mjs +174 -261
  132. package/telegram-plugin/dist/noise.mjs +1 -19
  133. package/worker-host.mjs +13747 -16824
  134. package/bin/profile-handoff.cjs +0 -152
  135. package/bin/profile-launch.cjs +0 -120
  136. package/dist-web/assets/DesignSystemView-DwK1aQMo.js +0 -12
  137. package/dist-web/assets/DesignSystemView-szjTj7ns.css +0 -1
  138. package/dist-web/assets/channel-BnluexuN.js +0 -1
  139. package/dist-web/assets/classDiagram-JCYQIIEL-BliFdSD9.js +0 -1
  140. package/dist-web/assets/classDiagram-v2-OCEON4UE-BliFdSD9.js +0 -1
  141. package/dist-web/assets/stateDiagram-v2-UEYNNEHI-fVkQcACW.js +0 -1
  142. package/dist-web/assets/swimlanesDiagram-ULZ7WXOC-Bg-BiZJQ.js +0 -8
@@ -1,122 +1,122 @@
1
- 'use strict';
2
- const fs = require('node:fs');
3
- const path = require('node:path');
4
- const os = require('node:os');
5
- const vm = require('node:vm');
6
- const assert = require('node:assert/strict');
7
-
8
- function functionSource(source, name) {
9
- const found = source.match(new RegExp('^function ' + name + '\\([\\s\\S]*?^\\}', 'm'))?.[0];
10
- if (!found) throw new Error('Missing executable contract: ' + name);
11
- return found;
12
- }
13
-
14
- function verifyAgentBehavior(packageRoot, reportParent) {
15
- const parent = reportParent || path.join(os.homedir(), '.blun', 'diagnostics', 'package-contracts');
16
- fs.mkdirSync(parent, { recursive: true });
17
- const work = fs.mkdtempSync(path.join(parent, 'check-'));
18
- const passed = [];
19
- const notApplicable = [];
20
- const write = (file, data) => {
21
- fs.mkdirSync(path.dirname(file), { recursive: true });
22
- fs.writeFileSync(file, typeof data === 'string' ? data : JSON.stringify(data));
23
- };
24
- const read = file => JSON.parse(fs.readFileSync(file, 'utf8'));
25
- const core = fs.readFileSync(path.join(packageRoot, 'blun.mjs'), 'utf8');
26
- const plugin = require(path.join(packageRoot, 'bin/plugin-bootstrap.js'));
27
- const selected = require(path.join(packageRoot, 'bin/managed-plugin-selection.cjs'));
28
- const tools = require(path.join(packageRoot, 'bin/standard-tools-bootstrap.js'));
29
- const profile = path.join(work, 'profile');
30
- plugin.installManagedAgentSpinePlugin({ packageRoot, blunDir: profile });
31
- plugin.installManagedTelegramPlugin({ packageRoot, blunDir: profile });
32
- tools.seedStandardTools({ packageRoot, blunDir: profile, names: ['blun-language-guard'], env: {} });
33
- const registryFile = path.join(profile, 'plugins/installed.json');
34
- const registry = read(registryFile);
35
- const spine = registry.plugins.find(item => item.id === 'agent-spine');
36
- const telegram = registry.plugins.find(item => item.id === 'telegram');
37
- assert(spine?.enabled && telegram?.enabled, 'native startup omitted a bundled plugin');
38
- assert(fs.existsSync(path.join(spine.root, 'src/hook.js')));
39
- const mcpFile = path.join(profile, 'mcp.json');
40
- const mcp = read(mcpFile);
41
- const guard = mcp.mcpServers['blun-language-guard'];
42
- assert.equal(guard.enabled, true);
43
- assert.equal(guard.args[0], path.join(packageRoot, 'standard-tools/language-guard/blun_language_guard.py'));
44
- passed.push('bootstrap-components-register-spine-telegram-guard');
45
-
46
- spine.managedSelection = selected.describeAgentSpineSelection({ blunDir: profile, pluginRoot: spine.root });
47
- write(registryFile, registry);
48
- const before = fs.readFileSync(registryFile);
49
- plugin.installManagedAgentSpinePlugin({ packageRoot, blunDir: profile });
50
- assert.deepEqual(fs.readFileSync(registryFile), before);
51
- guard.enabled = false; write(mcpFile, mcp);
52
- tools.seedStandardTools({ packageRoot, blunDir: profile, names: ['blun-language-guard'], env: {} });
53
- assert.equal(read(mcpFile).mcpServers['blun-language-guard'].enabled, false);
54
- guard.args = [path.join(work, 'custom/blun_language_guard.py'), 'serve'];
55
- guard.env = { SYNTHETIC_CUSTOM_SETTING: 'retained' }; write(mcpFile, mcp);
56
- const custom = fs.readFileSync(mcpFile);
57
- tools.seedStandardTools({ packageRoot, blunDir: profile, names: ['blun-language-guard'], env: {} });
58
- assert.deepEqual(fs.readFileSync(mcpFile), custom);
59
- passed.push('repeat-start-preserves-selected-plugin-disabled-and-custom-guard');
60
-
61
- const sharedHome = path.join(work, 'old-user-home');
62
- const staleRoot = path.join(sharedHome, '.blun/plugins/managed/old-telegram');
63
- write(path.join(staleRoot, '.mcp.json'), { mcpServers: {} });
64
- write(path.join(staleRoot, 'dist/mcp-server.mjs'), '// Synthetic old installed entrypoint.\n');
65
- write(path.join(sharedHome, '.blun/plugins/installed.json'), { plugins: [{ id: 'telegram', enabled: true, root: staleRoot }] });
66
- const env = { BLUN_HOME: profile };
67
- const resolveRoot = vm.runInNewContext(functionSource(core, 'managedTelegramPluginRoot') + '\nmanagedTelegramPluginRoot;', {
68
- process: { env }, join: path.join, homedir: () => sharedHome,
69
- existsSync: fs.existsSync, readFileSync: fs.readFileSync,
70
- });
71
- assert.equal(resolveRoot(), telegram.root, 'bridge selected the old shared registration');
72
- telegram.enabled = false; write(registryFile, registry);
73
- assert.equal(resolveRoot(), undefined, 'disabled plugin was selected');
74
- telegram.enabled = true; write(registryFile, registry);
75
- passed.push('packed-host-selects-active-profile-not-stale-shared-copy');
76
-
77
- const bridge = fs.readFileSync(path.join(telegram.root, 'dist/bridge.mjs'), 'utf8');
78
- const access = { dmPolicy: 'allowlist', allowFrom: ['operator'], pending: {},
79
- groups: { '-1001': { requireMention: true, alwaysAllowFrom: ['operator'], allowFrom: [] } } };
80
- const gateHelpers = ['matchesAny', 'isMentioned', 'gate',
81
- ...(/^function connectionCode\(/m.test(bridge) ? ['connectionCode']
82
- : ['isGermanGroupBroadcast', 'botLoopGuardAllows'])];
83
- const gate = vm.runInNewContext(gateHelpers
84
- .map(name => functionSource(bridge, name)).join('\n') + '\ngate;', {
85
- readAccess: () => access, pruneExpired: () => false,
86
- saveAccess: () => { throw new Error('unexpected access change'); },
87
- BOT_COOLDOWN_MS: 15000, lastBotDelivery: new Map(),
88
- });
89
- const message = { chatId: '-1001', chatType: 'supergroup', text: 'Status for another participant', from: { id: 'operator' } };
90
- assert.equal(gate(message, 'ExampleBot').action, 'drop');
91
- assert.equal(gate({ ...message, from: { id: 'another-bot', isBot: true } }, 'ExampleBot').action, 'drop');
92
- assert.equal(gate({ ...message, text: '@ExampleBot inspect', entities: [{ type: 'mention', offset: 0, length: 11 }] }, 'ExampleBot').action, 'deliver');
93
- assert.equal(gate({ ...message, replyToUsername: 'ExampleBot' }, 'ExampleBot').action, 'deliver');
94
- assert.equal(gate({ ...message, chatType: 'private', chatId: 'operator' }, 'ExampleBot').action, 'deliver');
95
- passed.push('registered-bridge-drops-unmentioned-groups-preserves-mentions-replies-dms');
96
-
97
- const thinkingFile = path.join(packageRoot, 'bin/thinking-only-guard.cjs');
98
- if (fs.existsSync(thinkingFile)) {
99
- const thinking = require(thinkingFile);
100
- assert.equal(thinking.DEFAULT_THINKING_ONLY_LIMIT_MS, 120000);
101
- const original = { messages: [{ role: 'user', content: 'synthetic inspection' }] };
102
- assert.equal(thinking.forceAnswerParams(original).thinkingOnlyRecovery, true);
103
- assert(core.includes('thinkingOnlyRecovery') && core.includes('withThinking("off")'), 'thinking recovery integration missing');
104
- passed.push('bounded-thinking-recovery-retained-structural-only');
105
- } else {
106
- assert(fs.existsSync(path.join(packageRoot, 'bundled-agent-sources.json')), 'private thinking helper missing');
107
- notApplicable.push({ check: 'private-thinking-helper', reason: 'The regular CLI does not ship this private test API; no recovery or host proof.' });
108
- }
109
- const routing = require(path.join(packageRoot, 'bin/turn-thinking-policy.cjs'));
110
- for (const prompt of ['Which result file belongs to our task?', 'Quick status?', 'Do you remember our previous decision?']) {
111
- assert.equal(routing.selectThinkingEffortForTurn(prompt, 'user', 'off'), undefined,
112
- 'continuity request lost its project context');
113
- }
114
- assert.equal(routing.selectThinkingEffortForTurn('Hi', 'user'), 'low');
115
- passed.push('continuity-requests-retain-normal-context-routing');
116
- const report = { verified: true, work, passed, notApplicable, launcherAutomaticRegistrationTested: false, modelApplicationTested: false,
117
- hubCompletenessTested: false, realProfilesChanged: false };
118
- write(path.join(work, 'report.json'), report);
119
- return report;
120
- }
121
-
122
- module.exports = { verifyAgentBehavior, functionSource };
1
+ 'use strict';
2
+ const fs = require('node:fs');
3
+ const path = require('node:path');
4
+ const os = require('node:os');
5
+ const vm = require('node:vm');
6
+ const assert = require('node:assert/strict');
7
+
8
+ function functionSource(source, name) {
9
+ const found = source.match(new RegExp('^function ' + name + '\\([\\s\\S]*?^\\}', 'm'))?.[0];
10
+ if (!found) throw new Error('Missing executable contract: ' + name);
11
+ return found;
12
+ }
13
+
14
+ function verifyAgentBehavior(packageRoot, reportParent) {
15
+ const parent = reportParent || path.join(os.homedir(), '.blun', 'diagnostics', 'package-contracts');
16
+ fs.mkdirSync(parent, { recursive: true });
17
+ const work = fs.mkdtempSync(path.join(parent, 'check-'));
18
+ const passed = [];
19
+ const notApplicable = [];
20
+ const write = (file, data) => {
21
+ fs.mkdirSync(path.dirname(file), { recursive: true });
22
+ fs.writeFileSync(file, typeof data === 'string' ? data : JSON.stringify(data));
23
+ };
24
+ const read = file => JSON.parse(fs.readFileSync(file, 'utf8'));
25
+ const core = fs.readFileSync(path.join(packageRoot, 'blun.mjs'), 'utf8');
26
+ const plugin = require(path.join(packageRoot, 'bin/plugin-bootstrap.js'));
27
+ const selected = require(path.join(packageRoot, 'bin/managed-plugin-selection.cjs'));
28
+ const tools = require(path.join(packageRoot, 'bin/standard-tools-bootstrap.js'));
29
+ const profile = path.join(work, 'profile');
30
+ plugin.installManagedAgentSpinePlugin({ packageRoot, blunDir: profile });
31
+ plugin.installManagedTelegramPlugin({ packageRoot, blunDir: profile });
32
+ tools.seedStandardTools({ packageRoot, blunDir: profile, names: ['blun-language-guard'], env: {} });
33
+ const registryFile = path.join(profile, 'plugins/installed.json');
34
+ const registry = read(registryFile);
35
+ const spine = registry.plugins.find(item => item.id === 'agent-spine');
36
+ const telegram = registry.plugins.find(item => item.id === 'telegram');
37
+ assert(spine?.enabled && telegram?.enabled, 'native startup omitted a bundled plugin');
38
+ assert(fs.existsSync(path.join(spine.root, 'src/hook.js')));
39
+ const mcpFile = path.join(profile, 'mcp.json');
40
+ const mcp = read(mcpFile);
41
+ const guard = mcp.mcpServers['blun-language-guard'];
42
+ assert.equal(guard.enabled, true);
43
+ assert.equal(guard.args[0], path.join(packageRoot, 'standard-tools/language-guard/blun_language_guard.py'));
44
+ passed.push('bootstrap-components-register-spine-telegram-guard');
45
+
46
+ spine.managedSelection = selected.describeAgentSpineSelection({ blunDir: profile, pluginRoot: spine.root });
47
+ write(registryFile, registry);
48
+ const before = fs.readFileSync(registryFile);
49
+ plugin.installManagedAgentSpinePlugin({ packageRoot, blunDir: profile });
50
+ assert.deepEqual(fs.readFileSync(registryFile), before);
51
+ guard.enabled = false; write(mcpFile, mcp);
52
+ tools.seedStandardTools({ packageRoot, blunDir: profile, names: ['blun-language-guard'], env: {} });
53
+ assert.equal(read(mcpFile).mcpServers['blun-language-guard'].enabled, false);
54
+ guard.args = [path.join(work, 'custom/blun_language_guard.py'), 'serve'];
55
+ guard.env = { SYNTHETIC_CUSTOM_SETTING: 'retained' }; write(mcpFile, mcp);
56
+ const custom = fs.readFileSync(mcpFile);
57
+ tools.seedStandardTools({ packageRoot, blunDir: profile, names: ['blun-language-guard'], env: {} });
58
+ assert.deepEqual(fs.readFileSync(mcpFile), custom);
59
+ passed.push('repeat-start-preserves-selected-plugin-disabled-and-custom-guard');
60
+
61
+ const sharedHome = path.join(work, 'old-user-home');
62
+ const staleRoot = path.join(sharedHome, '.blun/plugins/managed/old-telegram');
63
+ write(path.join(staleRoot, '.mcp.json'), { mcpServers: {} });
64
+ write(path.join(staleRoot, 'dist/mcp-server.mjs'), '// Synthetic old installed entrypoint.\n');
65
+ write(path.join(sharedHome, '.blun/plugins/installed.json'), { plugins: [{ id: 'telegram', enabled: true, root: staleRoot }] });
66
+ const env = { BLUN_HOME: profile };
67
+ const resolveRoot = vm.runInNewContext(functionSource(core, 'managedTelegramPluginRoot') + '\nmanagedTelegramPluginRoot;', {
68
+ process: { env }, join: path.join, homedir: () => sharedHome,
69
+ existsSync: fs.existsSync, readFileSync: fs.readFileSync,
70
+ });
71
+ assert.equal(resolveRoot(), telegram.root, 'bridge selected the old shared registration');
72
+ telegram.enabled = false; write(registryFile, registry);
73
+ assert.equal(resolveRoot(), undefined, 'disabled plugin was selected');
74
+ telegram.enabled = true; write(registryFile, registry);
75
+ passed.push('packed-host-selects-active-profile-not-stale-shared-copy');
76
+
77
+ const bridge = fs.readFileSync(path.join(telegram.root, 'dist/bridge.mjs'), 'utf8');
78
+ const access = { dmPolicy: 'allowlist', allowFrom: ['operator'], pending: {},
79
+ groups: { '-1001': { requireMention: true, alwaysAllowFrom: ['operator'], allowFrom: [] } } };
80
+ const gateHelpers = ['matchesAny', 'isMentioned', 'gate',
81
+ ...(/^function connectionCode\(/m.test(bridge) ? ['connectionCode']
82
+ : ['isGermanGroupBroadcast', 'botLoopGuardAllows'])];
83
+ const gate = vm.runInNewContext(gateHelpers
84
+ .map(name => functionSource(bridge, name)).join('\n') + '\ngate;', {
85
+ readAccess: () => access, pruneExpired: () => false,
86
+ saveAccess: () => { throw new Error('unexpected access change'); },
87
+ BOT_COOLDOWN_MS: 15000, lastBotDelivery: new Map(),
88
+ });
89
+ const message = { chatId: '-1001', chatType: 'supergroup', text: 'Status for another participant', from: { id: 'operator' } };
90
+ assert.equal(gate(message, 'ExampleBot').action, 'drop');
91
+ assert.equal(gate({ ...message, from: { id: 'another-bot', isBot: true } }, 'ExampleBot').action, 'drop');
92
+ assert.equal(gate({ ...message, text: '@ExampleBot inspect', entities: [{ type: 'mention', offset: 0, length: 11 }] }, 'ExampleBot').action, 'deliver');
93
+ assert.equal(gate({ ...message, replyToUsername: 'ExampleBot' }, 'ExampleBot').action, 'deliver');
94
+ assert.equal(gate({ ...message, chatType: 'private', chatId: 'operator' }, 'ExampleBot').action, 'deliver');
95
+ passed.push('registered-bridge-drops-unmentioned-groups-preserves-mentions-replies-dms');
96
+
97
+ const thinkingFile = path.join(packageRoot, 'bin/thinking-only-guard.cjs');
98
+ if (fs.existsSync(thinkingFile)) {
99
+ const thinking = require(thinkingFile);
100
+ assert.equal(thinking.DEFAULT_THINKING_ONLY_LIMIT_MS, 120000);
101
+ const original = { messages: [{ role: 'user', content: 'synthetic inspection' }] };
102
+ assert.equal(thinking.forceAnswerParams(original).thinkingOnlyRecovery, true);
103
+ assert(core.includes('thinkingOnlyRecovery') && core.includes('withThinking("off")'), 'thinking recovery integration missing');
104
+ passed.push('bounded-thinking-recovery-retained-structural-only');
105
+ } else {
106
+ assert(fs.existsSync(path.join(packageRoot, 'bundled-agent-sources.json')), 'private thinking helper missing');
107
+ notApplicable.push({ check: 'private-thinking-helper', reason: 'The regular CLI does not ship this private test API; no recovery or host proof.' });
108
+ }
109
+ const routing = require(path.join(packageRoot, 'bin/turn-thinking-policy.cjs'));
110
+ for (const prompt of ['Which result file belongs to our task?', 'Quick status?', 'Do you remember our previous decision?']) {
111
+ assert.equal(routing.selectThinkingEffortForTurn(prompt, 'user', 'off'), undefined,
112
+ 'continuity request lost its project context');
113
+ }
114
+ assert.equal(routing.selectThinkingEffortForTurn('Hi', 'user'), 'low');
115
+ passed.push('continuity-requests-retain-normal-context-routing');
116
+ const report = { verified: true, work, passed, notApplicable, launcherAutomaticRegistrationTested: false, modelApplicationTested: false,
117
+ hubCompletenessTested: false, realProfilesChanged: false };
118
+ write(path.join(work, 'report.json'), report);
119
+ return report;
120
+ }
121
+
122
+ module.exports = { verifyAgentBehavior, functionSource };
@@ -1,104 +1,104 @@
1
- 'use strict';
2
-
3
- const fs = require('node:fs');
4
- const path = require('node:path');
5
- const { createHash } = require('node:crypto');
6
-
7
- const RECEIPT = 'agent-components.json';
8
- const SCHEMA = 'blun.bundled-agent-components/v1';
9
- const REQUIRED = [
10
- 'blun.mjs',
11
- 'telegram-plugin/dist/bridge.mjs',
12
- 'bin/thinking-only-guard.cjs',
13
- 'bin/turn-thinking-policy.cjs',
14
- 'bin/context-performance-policy.cjs',
15
- 'bin/verify-agent-behavior.cjs',
16
- 'agent-spine-plugin/blun.plugin.json',
17
- 'agent-spine-plugin/src/hook.js',
18
- 'agent-spine-plugin/src/mcp.js',
19
- 'agent-spine-plugin/skills/agent-spine/SKILL.md',
20
- 'standard-skills/translate-native/SKILL.md',
21
- 'standard-tools/language-guard/blun_language_guard.py',
22
- 'standard-tools/language-guard/check_diacritics.py',
23
- 'standard-tools/language-guard/guard_service_client.py',
24
- 'standard-tools/language-guard/language_quality.py',
25
- 'standard-tools/language-guard/translation_guard.py',
26
- 'bin/plugin-bootstrap.js',
27
- 'bin/managed-plugin-selection.cjs',
28
- 'bin/standard-tools-bootstrap.js',
29
- 'bin/launcher-runtime.js',
30
- ];
31
- const PREFIXES = ['agent-spine-plugin/', 'standard-skills/translate-native/', 'standard-tools/language-guard/'];
32
- const sha256 = bytes => createHash('sha256').update(bytes).digest('hex');
33
-
34
- function inventory(root, prefix = '', excluded = []) {
35
- const result = {};
36
- for (const name of fs.readdirSync(root).sort()) {
37
- const file = path.join(root, name);
38
- const relative = prefix + name;
39
- if (excluded.some(item => relative === item || relative.startsWith(item + '/'))) continue;
40
- const stat = fs.lstatSync(file);
41
- if (stat.isSymbolicLink()) throw new Error('Linked component path: ' + relative);
42
- if (stat.isDirectory()) Object.assign(result, inventory(file, relative + '/', excluded));
43
- else if (stat.isFile()) result[relative] = sha256(fs.readFileSync(file));
44
- else throw new Error('Unsupported component path: ' + relative);
45
- }
46
- return result;
47
- }
48
-
49
- function safeFile(root, relative) {
50
- if (typeof relative !== 'string' || relative.includes('\\') || relative.includes(':')
51
- || relative.split('/').some(part => !part || part === '.' || part === '..')) {
52
- throw new Error('Invalid component path');
53
- }
54
- let current = path.resolve(root);
55
- for (const part of relative.split('/')) {
56
- current = path.join(current, part);
57
- if (fs.lstatSync(current).isSymbolicLink()) throw new Error('Linked component path: ' + relative);
58
- }
59
- return current;
60
- }
61
-
62
- function verifyAgentComponents(packageRoot) {
63
- const receipt = JSON.parse(fs.readFileSync(path.join(packageRoot, RECEIPT), 'utf8'));
64
- if (receipt.schema !== SCHEMA || !receipt.files || typeof receipt.files !== 'object'
65
- || Array.isArray(receipt.files)) throw new Error('Invalid bundled component receipt');
66
- for (const name of ['agentSpine', 'translateNative']) {
67
- if (!/^[a-f0-9]{40}$/.test(receipt.sources?.[name]?.commit || '')) {
68
- throw new Error('Missing bundled component source: ' + name);
69
- }
70
- }
71
- for (const name of REQUIRED) {
72
- if (!Object.hasOwn(receipt.files, name)) throw new Error('Missing bundled component: ' + name);
73
- }
74
- for (const [relative, expected] of Object.entries(receipt.files)) {
75
- if (!/^[a-f0-9]{64}$/.test(expected) || sha256(fs.readFileSync(safeFile(packageRoot, relative))) !== expected) {
76
- throw new Error('Bundled component digest mismatch: ' + relative);
77
- }
78
- }
79
- for (const prefix of PREFIXES) {
80
- const actual = Object.keys(inventory(safeFile(packageRoot, prefix.slice(0, -1)), prefix));
81
- const expected = Object.keys(receipt.files).filter(file => file.startsWith(prefix)).sort();
82
- if (JSON.stringify(actual.sort()) !== JSON.stringify(expected)) throw new Error('Unexpected bundled component files: ' + prefix);
83
- }
84
- const manifest = JSON.parse(fs.readFileSync(path.join(packageRoot, 'package.json'), 'utf8'));
85
- if (manifest.scripts?.prepack !== 'node bin/verify-agent-components.cjs') {
86
- throw new Error('Bundled component prepack check missing');
87
- }
88
- const launcher = fs.readFileSync(path.join(packageRoot, 'bin/launcher-runtime.js'), 'utf8');
89
- if (!launcher.includes("require('./verify-agent-components.cjs').verifyAgentComponents(PKG);")) {
90
- throw new Error('Bundled component startup check missing');
91
- }
92
- return { verified: true, files: Object.keys(receipt.files).length, sources: receipt.sources };
93
- }
94
-
95
- if (require.main === module) {
96
- try {
97
- const root = path.resolve(__dirname, '..');
98
- console.log(JSON.stringify(verifyAgentComponents(root)));
99
- console.log(JSON.stringify(require('./verify-agent-behavior.cjs').verifyAgentBehavior(root)));
100
- }
101
- catch (error) { console.error(error.message); process.exitCode = 1; }
102
- }
103
-
104
- module.exports = { inventory, sha256, verifyAgentComponents, SCHEMA, RECEIPT, REQUIRED, PREFIXES };
1
+ 'use strict';
2
+
3
+ const fs = require('node:fs');
4
+ const path = require('node:path');
5
+ const { createHash } = require('node:crypto');
6
+
7
+ const RECEIPT = 'agent-components.json';
8
+ const SCHEMA = 'blun.bundled-agent-components/v1';
9
+ const REQUIRED = [
10
+ 'blun.mjs',
11
+ 'telegram-plugin/dist/bridge.mjs',
12
+ 'bin/thinking-only-guard.cjs',
13
+ 'bin/turn-thinking-policy.cjs',
14
+ 'bin/context-performance-policy.cjs',
15
+ 'bin/verify-agent-behavior.cjs',
16
+ 'agent-spine-plugin/blun.plugin.json',
17
+ 'agent-spine-plugin/src/hook.js',
18
+ 'agent-spine-plugin/src/mcp.js',
19
+ 'agent-spine-plugin/skills/agent-spine/SKILL.md',
20
+ 'standard-skills/translate-native/SKILL.md',
21
+ 'standard-tools/language-guard/blun_language_guard.py',
22
+ 'standard-tools/language-guard/check_diacritics.py',
23
+ 'standard-tools/language-guard/guard_service_client.py',
24
+ 'standard-tools/language-guard/language_quality.py',
25
+ 'standard-tools/language-guard/translation_guard.py',
26
+ 'bin/plugin-bootstrap.js',
27
+ 'bin/managed-plugin-selection.cjs',
28
+ 'bin/standard-tools-bootstrap.js',
29
+ 'bin/launcher-runtime.js',
30
+ ];
31
+ const PREFIXES = ['agent-spine-plugin/', 'standard-skills/translate-native/', 'standard-tools/language-guard/'];
32
+ const sha256 = bytes => createHash('sha256').update(bytes).digest('hex');
33
+
34
+ function inventory(root, prefix = '', excluded = []) {
35
+ const result = {};
36
+ for (const name of fs.readdirSync(root).sort()) {
37
+ const file = path.join(root, name);
38
+ const relative = prefix + name;
39
+ if (excluded.some(item => relative === item || relative.startsWith(item + '/'))) continue;
40
+ const stat = fs.lstatSync(file);
41
+ if (stat.isSymbolicLink()) throw new Error('Linked component path: ' + relative);
42
+ if (stat.isDirectory()) Object.assign(result, inventory(file, relative + '/', excluded));
43
+ else if (stat.isFile()) result[relative] = sha256(fs.readFileSync(file));
44
+ else throw new Error('Unsupported component path: ' + relative);
45
+ }
46
+ return result;
47
+ }
48
+
49
+ function safeFile(root, relative) {
50
+ if (typeof relative !== 'string' || relative.includes('\\') || relative.includes(':')
51
+ || relative.split('/').some(part => !part || part === '.' || part === '..')) {
52
+ throw new Error('Invalid component path');
53
+ }
54
+ let current = path.resolve(root);
55
+ for (const part of relative.split('/')) {
56
+ current = path.join(current, part);
57
+ if (fs.lstatSync(current).isSymbolicLink()) throw new Error('Linked component path: ' + relative);
58
+ }
59
+ return current;
60
+ }
61
+
62
+ function verifyAgentComponents(packageRoot) {
63
+ const receipt = JSON.parse(fs.readFileSync(path.join(packageRoot, RECEIPT), 'utf8'));
64
+ if (receipt.schema !== SCHEMA || !receipt.files || typeof receipt.files !== 'object'
65
+ || Array.isArray(receipt.files)) throw new Error('Invalid bundled component receipt');
66
+ for (const name of ['agentSpine', 'translateNative']) {
67
+ if (!/^[a-f0-9]{40}$/.test(receipt.sources?.[name]?.commit || '')) {
68
+ throw new Error('Missing bundled component source: ' + name);
69
+ }
70
+ }
71
+ for (const name of REQUIRED) {
72
+ if (!Object.hasOwn(receipt.files, name)) throw new Error('Missing bundled component: ' + name);
73
+ }
74
+ for (const [relative, expected] of Object.entries(receipt.files)) {
75
+ if (!/^[a-f0-9]{64}$/.test(expected) || sha256(fs.readFileSync(safeFile(packageRoot, relative))) !== expected) {
76
+ throw new Error('Bundled component digest mismatch: ' + relative);
77
+ }
78
+ }
79
+ for (const prefix of PREFIXES) {
80
+ const actual = Object.keys(inventory(safeFile(packageRoot, prefix.slice(0, -1)), prefix));
81
+ const expected = Object.keys(receipt.files).filter(file => file.startsWith(prefix)).sort();
82
+ if (JSON.stringify(actual.sort()) !== JSON.stringify(expected)) throw new Error('Unexpected bundled component files: ' + prefix);
83
+ }
84
+ const manifest = JSON.parse(fs.readFileSync(path.join(packageRoot, 'package.json'), 'utf8'));
85
+ if (manifest.scripts?.prepack !== 'node bin/verify-agent-components.cjs') {
86
+ throw new Error('Bundled component prepack check missing');
87
+ }
88
+ const launcher = fs.readFileSync(path.join(packageRoot, 'bin/launcher-runtime.js'), 'utf8');
89
+ if (!launcher.includes("require('./verify-agent-components.cjs').verifyAgentComponents(PKG);")) {
90
+ throw new Error('Bundled component startup check missing');
91
+ }
92
+ return { verified: true, files: Object.keys(receipt.files).length, sources: receipt.sources };
93
+ }
94
+
95
+ if (require.main === module) {
96
+ try {
97
+ const root = path.resolve(__dirname, '..');
98
+ console.log(JSON.stringify(verifyAgentComponents(root)));
99
+ console.log(JSON.stringify(require('./verify-agent-behavior.cjs').verifyAgentBehavior(root)));
100
+ }
101
+ catch (error) { console.error(error.message); process.exitCode = 1; }
102
+ }
103
+
104
+ module.exports = { inventory, sha256, verifyAgentComponents, SCHEMA, RECEIPT, REQUIRED, PREFIXES };