blun-king-cli 9.1.602 → 9.1.604

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 (124) hide show
  1. package/CHANGELOG.md +118 -99
  2. package/LIESMICH.txt +74 -57
  3. package/README.md +119 -76
  4. package/bin/agentspine-king-goal-inbox.mjs +127 -127
  5. package/bin/agentspine-king-goal-intake.mjs +106 -106
  6. package/bin/agentspine-king-host-runner.mjs +109 -109
  7. package/bin/agentspine-king-snapshot-policy.cjs +80 -80
  8. package/bin/agentspine-king-status-policy.mjs +226 -226
  9. package/bin/agentspine-king-worker-host.mjs +160 -160
  10. package/bin/core-bootstrap.js +77 -77
  11. package/bin/launcher-mode.js +93 -93
  12. package/bin/launcher-restart-policy.cjs +131 -131
  13. package/bin/launcher-runtime.js +491 -491
  14. package/bin/managed-node.js +186 -186
  15. package/bin/managed-plugin-selection.cjs +115 -115
  16. package/bin/native-module-repair.js +201 -201
  17. package/bin/node-runtime.js +34 -34
  18. package/bin/node-version.js +49 -49
  19. package/bin/plugin-bootstrap.js +162 -162
  20. package/bin/private-paths.js +1606 -1606
  21. package/bin/runtime-exit-ledger.cjs +144 -144
  22. package/bin/runtime-exit-ledger.d.cts +23 -23
  23. package/bin/standard-tools-bootstrap.js +272 -272
  24. package/bin/turn-thinking-policy.cjs +82 -82
  25. package/bin/update-copy.js +200 -200
  26. package/bin/update-lease.js +597 -597
  27. package/bin/update-notice.js +1568 -1568
  28. package/bin/verify-agent-behavior.cjs +122 -122
  29. package/bin/verify-agent-components.cjs +104 -104
  30. package/bin/verify-bundled-agent-sources.cjs +98 -76
  31. package/bin/windows-node-crash-dump.cjs +289 -289
  32. package/blun.mjs +8041 -6407
  33. package/codebase-index/codebase_index.py +470 -470
  34. package/dist-web/assets/{CodeBlockNode-CXnTLMNr.js → CodeBlockNode-TKKkJX8L.js} +1 -1
  35. package/dist-web/assets/{CodeBlockShell.vue_vue_type_style_index_0_lang-CCF8Ntnq.js → CodeBlockShell.vue_vue_type_style_index_0_lang-CUj2Ds5x.js} +1 -1
  36. package/dist-web/assets/DesignSystemView-DwK1aQMo.js +12 -0
  37. package/dist-web/assets/DesignSystemView-szjTj7ns.css +1 -0
  38. package/dist-web/assets/{Tooltip-CFQEzqC-.js → Tooltip-CTWv7WCU.js} +1 -1
  39. package/dist-web/assets/{abnfDiagram-N423BO3Z-Z1APZG5m.js → abnfDiagram-N423BO3Z-DA8V8Zjh.js} +1 -1
  40. package/dist-web/assets/{arc-C4VyWloT.js → arc-YtddKbdY.js} +1 -1
  41. package/dist-web/assets/{architectureDiagram-T3A2C74G-DpddbUcM.js → architectureDiagram-T3A2C74G-CGEiObmo.js} +1 -1
  42. package/dist-web/assets/{blockDiagram-VBNYF7ZC-BqccOSMr.js → blockDiagram-VBNYF7ZC-Bd5Vt50e.js} +1 -1
  43. package/dist-web/assets/{c4Diagram-5PPSVZJV-OHAcJrTy.js → c4Diagram-5PPSVZJV-D2ApPM54.js} +1 -1
  44. package/dist-web/assets/channel-BnluexuN.js +1 -0
  45. package/dist-web/assets/{chunk-2GRJ4B5K-DqJHs8Fw.js → chunk-2GRJ4B5K-BDcIEZSl.js} +1 -1
  46. package/dist-web/assets/{chunk-2Q5K7J3B-B1nCd91o.js → chunk-2Q5K7J3B-6aSabCX7.js} +1 -1
  47. package/dist-web/assets/{chunk-5RXB4S5H-neG70kZx.js → chunk-5RXB4S5H-BFRQvIJS.js} +1 -1
  48. package/dist-web/assets/{chunk-5VM5RSS4-l7biGqeH.js → chunk-5VM5RSS4-ChAa-DNY.js} +1 -1
  49. package/dist-web/assets/{chunk-6Q2QTUOP-BO-F11Gs.js → chunk-6Q2QTUOP-BIfrb2Jc.js} +1 -1
  50. package/dist-web/assets/{chunk-GF5L2VYU-BtC69fw0.js → chunk-GF5L2VYU-BR0gvtWI.js} +1 -1
  51. package/dist-web/assets/{chunk-JWPE2WC7-DIOubEqr.js → chunk-JWPE2WC7-BgXKsx5E.js} +1 -1
  52. package/dist-web/assets/{chunk-KBJHAD2P-DDUeH_Ba.js → chunk-KBJHAD2P-I8mrZhpz.js} +1 -1
  53. package/dist-web/assets/{chunk-RYQCIY6F-B9gFMVbS.js → chunk-RYQCIY6F-BMPaem92.js} +1 -1
  54. package/dist-web/assets/{chunk-XXDRQBXY-Bn1Xz6TQ.js → chunk-XXDRQBXY-COy0J-0j.js} +1 -1
  55. package/dist-web/assets/classDiagram-JCYQIIEL-BliFdSD9.js +1 -0
  56. package/dist-web/assets/classDiagram-v2-OCEON4UE-BliFdSD9.js +1 -0
  57. package/dist-web/assets/{cose-bilkent-JH36ORCC-DqigO3T4.js → cose-bilkent-JH36ORCC-MosTTRdo.js} +1 -1
  58. package/dist-web/assets/{cynefin-VYW2F7L2-rpD93tQ-.js → cynefin-VYW2F7L2-5tjPgrkP.js} +1 -1
  59. package/dist-web/assets/{cynefinDiagram-MW4NZA55-DbTqnkVJ.js → cynefinDiagram-MW4NZA55-CbDcdInR.js} +1 -1
  60. package/dist-web/assets/{dagre-VZM6K2ZE-DcM8aEVY.js → dagre-VZM6K2ZE-YaBom1ZH.js} +1 -1
  61. package/dist-web/assets/{diagram-7IWD3JNH-UymgsBNa.js → diagram-7IWD3JNH-Da4m-Hgz.js} +1 -1
  62. package/dist-web/assets/{diagram-B4RE2ZJO-BiYu5WPF.js → diagram-B4RE2ZJO-CAUUegxg.js} +1 -1
  63. package/dist-web/assets/{diagram-LBJQPF4R-BD3kkgOO.js → diagram-LBJQPF4R-o21mWKG4.js} +1 -1
  64. package/dist-web/assets/{diagram-Q27KOJAE-D1mYQ_cI.js → diagram-Q27KOJAE-BA3z3sXf.js} +1 -1
  65. package/dist-web/assets/{diagram-UB23O5K3-BtX8u6HG.js → diagram-UB23O5K3-CGRdSSDK.js} +1 -1
  66. package/dist-web/assets/{ebnfDiagram-BXEA7PRR-CBAnFkRF.js → ebnfDiagram-BXEA7PRR-Bw-iU9FP.js} +1 -1
  67. package/dist-web/assets/{erDiagram-JOGREHBK-3vKcWsxm.js → erDiagram-JOGREHBK-BwM1yiR0.js} +1 -1
  68. package/dist-web/assets/{flowDiagram-UKHOOZJN-D9wBbHm2.js → flowDiagram-UKHOOZJN-z05eOR1Q.js} +1 -1
  69. package/dist-web/assets/{ganttDiagram-PKOTCBZU-Cd_dViF-.js → ganttDiagram-PKOTCBZU-BMUubNTl.js} +1 -1
  70. package/dist-web/assets/{gitGraphDiagram-DS77QQ5N-BiEEtTo7.js → gitGraphDiagram-DS77QQ5N-Dccbi1wU.js} +1 -1
  71. package/dist-web/assets/{index-DdEakFJM.js → index-BBbC5LRF.js} +1 -1
  72. package/dist-web/assets/{index-BmKj7CB1.css → index-C9eDH3u-.css} +1 -1
  73. package/dist-web/assets/{index-Ba1OvLjf.js → index-D48p0M2S.js} +2 -2
  74. package/dist-web/assets/{index-CiPA8Kex.js → index-h0KEOmkU.js} +14 -14
  75. package/dist-web/assets/{index10-CNr4U-Nw.js → index10-Qbuw1N6u.js} +1 -1
  76. package/dist-web/assets/{index11-Dto_yZNz.js → index11-g8v025wN.js} +1 -1
  77. package/dist-web/assets/{index5-B2PldaO2.js → index5-BYNcV8BY.js} +1 -1
  78. package/dist-web/assets/{index6-CBdZmmyx.js → index6-BetANtnV.js} +1 -1
  79. package/dist-web/assets/{index7-Dz9oCsUF.js → index7-BcPbgCKN.js} +1 -1
  80. package/dist-web/assets/{index8-B6k25jtU.js → index8-B1lp-RvF.js} +2 -2
  81. package/dist-web/assets/{infoDiagram-6WML65LV-D4ph2sIH.js → infoDiagram-6WML65LV-BF_pbUnN.js} +1 -1
  82. package/dist-web/assets/{ishikawaDiagram-WSZJBQD7-DjKjbMm9.js → ishikawaDiagram-WSZJBQD7-Dedv4urS.js} +1 -1
  83. package/dist-web/assets/{journeyDiagram-NVQOT4AX-DttHDfPQ.js → journeyDiagram-NVQOT4AX-B1Ex3b6o.js} +1 -1
  84. package/dist-web/assets/{kanban-definition-27J2QSJJ-C5gB-W0d.js → kanban-definition-27J2QSJJ-BtCGliBH.js} +1 -1
  85. package/dist-web/assets/{linear-BRGe4-d6.js → linear-ByItc8Qr.js} +1 -1
  86. package/dist-web/assets/{mermaid.core-D-9Sx1No.js → mermaid.core-B1M2O7nN.js} +4 -4
  87. package/dist-web/assets/{mindmap-definition-FAOFIHXS-Cg_hoVIq.js → mindmap-definition-FAOFIHXS-D0UHKUyj.js} +1 -1
  88. package/dist-web/assets/{pegDiagram-VL7TDLO6-CSX9hitD.js → pegDiagram-VL7TDLO6-D7zYCVmG.js} +1 -1
  89. package/dist-web/assets/{pieDiagram-7S7Q4E2Y-0L0mxahZ.js → pieDiagram-7S7Q4E2Y-Bd0i9CHa.js} +1 -1
  90. package/dist-web/assets/{quadrantDiagram-CIZ2JOQS-U483z0er.js → quadrantDiagram-CIZ2JOQS-MG-Ug47d.js} +1 -1
  91. package/dist-web/assets/{railroadDiagram-AXF67PYL-D80a2_DJ.js → railroadDiagram-AXF67PYL-AZdl90Gj.js} +1 -1
  92. package/dist-web/assets/{requirementDiagram-LRYGKXZP-CeyHfq8t.js → requirementDiagram-LRYGKXZP-DjplF9kt.js} +1 -1
  93. package/dist-web/assets/{sankeyDiagram-W5VNT64P-CCISkkrR.js → sankeyDiagram-W5VNT64P-Bi9Zgfjs.js} +1 -1
  94. package/dist-web/assets/{sequenceDiagram-SI44F4Z6-jQfm2XS7.js → sequenceDiagram-SI44F4Z6-ChEpdCvb.js} +1 -1
  95. package/dist-web/assets/{sizeCapture-X5ZJPWSS-lRZsmCoS.js → sizeCapture-X5ZJPWSS-B5RFUdaa.js} +1 -1
  96. package/dist-web/assets/{stateDiagram-OKZ733FA-ClCgTdUm.js → stateDiagram-OKZ733FA-BkRblSKp.js} +1 -1
  97. package/dist-web/assets/stateDiagram-v2-UEYNNEHI-fVkQcACW.js +1 -0
  98. package/dist-web/assets/{swimlanes-SLNWSIFB-BCdOUN05.js → swimlanes-SLNWSIFB-DLXH6Zhc.js} +2 -2
  99. package/dist-web/assets/swimlanesDiagram-ULZ7WXOC-Bg-BiZJQ.js +8 -0
  100. package/dist-web/assets/{timeline-definition-Z64GVDOM-DpWutSKa.js → timeline-definition-Z64GVDOM-C_N88siO.js} +1 -1
  101. package/dist-web/assets/{vennDiagram-T6HMQDX7-DVC5JVsc.js → vennDiagram-T6HMQDX7-DMsnvDt0.js} +1 -1
  102. package/dist-web/assets/{vue.runtime.esm-bundler-BwwRtQwQ.js → vue.runtime.esm-bundler-B-3uIy85.js} +1 -1
  103. package/dist-web/assets/{wardleyDiagram-T6FBY63Y-BK2mrq7L.js → wardleyDiagram-T6FBY63Y-C9SJIPzq.js} +1 -1
  104. package/dist-web/assets/{xychartDiagram-ELKLHX3M-DUJu9F7-.js → xychartDiagram-ELKLHX3M-DZLWA6Go.js} +1 -1
  105. package/dist-web/index.html +32 -32
  106. package/dist-web/vis/index.html.gz +0 -0
  107. package/package.json +1 -1
  108. package/scripts/fix-node-pty-perms.js +53 -53
  109. package/standard-skills/blun-app-design-system/SKILL.md +30 -30
  110. package/standard-skills/blun-app-design-system/provenance.json +17 -17
  111. package/standard-skills/blun-app-design-system/references/tokens.md +48 -48
  112. package/standard-skills/blun-app-design-system/scripts/check-blun-design.mjs +49 -49
  113. package/telegram-plugin/blun.plugin.json +20 -20
  114. package/telegram-plugin/commands/access.md +37 -37
  115. package/telegram-plugin/dist/bridge.mjs +117 -100
  116. package/telegram-plugin/dist/mcp-server.mjs +185 -161
  117. package/worker-host.mjs +1458 -1022
  118. package/dist-web/assets/DesignSystemView-6HgAUEmC.css +0 -1
  119. package/dist-web/assets/DesignSystemView-BbMOu-XO.js +0 -12
  120. package/dist-web/assets/channel-B2VOQOI-.js +0 -1
  121. package/dist-web/assets/classDiagram-JCYQIIEL-BA1TjAxj.js +0 -1
  122. package/dist-web/assets/classDiagram-v2-OCEON4UE-BA1TjAxj.js +0 -1
  123. package/dist-web/assets/stateDiagram-v2-UEYNNEHI-fi_lrTAx.js +0 -1
  124. package/dist-web/assets/swimlanesDiagram-ULZ7WXOC-BUd_uYK3.js +0 -8
@@ -1,162 +1,162 @@
1
- 'use strict';
2
-
3
- const { randomUUID } = require('node:crypto');
4
- const fs = require('node:fs');
5
- const path = require('node:path');
6
- const { selectedAgentSpineRoot, describeAgentSpineSelection, copySelectedAgentSpinePlugin } = require('./managed-plugin-selection.cjs');
7
-
8
- const {
9
- ensurePrivateDirectory,
10
- securePrivateFile,
11
- writePrivateFile,
12
- } = require('./private-paths');
13
-
14
- function isObject(value) {
15
- return typeof value === 'object' && value !== null && !Array.isArray(value);
16
- }
17
-
18
- function readInstalledPlugins(filePath) {
19
- if (!fs.existsSync(filePath)) return { version: 1, plugins: [] };
20
-
21
- const parsed = JSON.parse(fs.readFileSync(filePath, 'utf8'));
22
- if (!isObject(parsed) || parsed.version !== 1 || !Array.isArray(parsed.plugins)
23
- || !parsed.plugins.every(isObject)) {
24
- throw new Error(`Ungültige Plugin-Konfiguration: ${filePath}`);
25
- }
26
- return parsed;
27
- }
28
-
29
- function updatedInstalledPlugins(installed, pluginRoot, now, pluginId = 'telegram') {
30
- const previous = installed.plugins.find((plugin) => plugin.id === pluginId);
31
- const previousCapabilities = isObject(previous?.capabilities) ? previous.capabilities : {};
32
- const previousMcpServers = isObject(previousCapabilities.mcpServers)
33
- ? previousCapabilities.mcpServers : {};
34
- const previousTelegramMcp = isObject(previousMcpServers[pluginId])
35
- ? previousMcpServers[pluginId] : {};
36
- const telegram = {
37
- ...previous,
38
- id: pluginId,
39
- root: pluginRoot,
40
- source: 'local-path',
41
- enabled: true,
42
- installedAt: typeof previous?.installedAt === 'string' && previous.installedAt.length > 0
43
- ? previous.installedAt : now,
44
- updatedAt: now,
45
- capabilities: {
46
- ...previousCapabilities,
47
- mcpServers: {
48
- ...previousMcpServers,
49
- [pluginId]: { ...previousTelegramMcp, enabled: previousTelegramMcp.enabled === false && pluginId === 'agent-spine' ? false : true },
50
- },
51
- },
52
- };
53
-
54
- const plugins = [];
55
- let replaced = false;
56
- for (const plugin of installed.plugins) {
57
- if (plugin.id !== pluginId) {
58
- plugins.push(plugin);
59
- } else if (!replaced) {
60
- plugins.push(telegram);
61
- replaced = true;
62
- }
63
- }
64
- if (!replaced) plugins.push(telegram);
65
- return { ...installed, version: 1, plugins };
66
- }
67
-
68
- function writeInstalledPlugins(filePath, installed) {
69
- const temporaryPath = path.join(
70
- path.dirname(filePath),
71
- `.${path.basename(filePath)}.${process.pid}.${randomUUID()}.tmp`,
72
- );
73
- try {
74
- writePrivateFile(temporaryPath, `${JSON.stringify(installed, null, 2)}\n`);
75
- fs.renameSync(temporaryPath, filePath);
76
- } catch (error) {
77
- fs.rmSync(temporaryPath, { force: true });
78
- throw error;
79
- }
80
- }
81
-
82
- function installManagedTelegramPlugin({ packageRoot, blunDir, now = new Date().toISOString() }) {
83
- const sourceRoot = path.join(packageRoot, 'telegram-plugin');
84
- const pluginsDir = path.join(blunDir, 'plugins');
85
- const managedDir = path.join(pluginsDir, 'managed');
86
- const pluginRoot = path.join(managedDir, 'telegram');
87
- const installedFile = path.join(pluginsDir, 'installed.json');
88
-
89
- ensurePrivateDirectory(pluginsDir);
90
- if (fs.existsSync(installedFile)) securePrivateFile(installedFile);
91
- const installed = readInstalledPlugins(installedFile);
92
- const updatedInstalled = updatedInstalledPlugins(installed, pluginRoot, now);
93
- ensurePrivateDirectory(managedDir);
94
- const nonce = `${process.pid}-${randomUUID()}`;
95
- const stagingRoot = path.join(managedDir, `.telegram-${nonce}.tmp`);
96
- const backupRoot = path.join(managedDir, `.telegram-${nonce}.bak`);
97
- let hadPreviousPlugin = false;
98
- let installedNewPlugin = false;
99
- try {
100
- if (fs.existsSync(pluginRoot)) {
101
- ensurePrivateDirectory(pluginRoot);
102
- hadPreviousPlugin = true;
103
- }
104
- fs.cpSync(sourceRoot, stagingRoot, { recursive: true, force: true });
105
- if (hadPreviousPlugin) fs.renameSync(pluginRoot, backupRoot);
106
- try {
107
- fs.renameSync(stagingRoot, pluginRoot);
108
- installedNewPlugin = true;
109
- writeInstalledPlugins(installedFile, updatedInstalled);
110
- } catch (error) {
111
- if (installedNewPlugin) fs.rmSync(pluginRoot, { recursive: true, force: true });
112
- if (hadPreviousPlugin && fs.existsSync(backupRoot)) {
113
- fs.renameSync(backupRoot, pluginRoot);
114
- }
115
- throw error;
116
- }
117
- if (hadPreviousPlugin) fs.rmSync(backupRoot, { recursive: true, force: true });
118
- } finally {
119
- fs.rmSync(stagingRoot, { recursive: true, force: true });
120
- }
121
-
122
- return { installedFile, pluginRoot };
123
- }
124
-
125
- function installManagedAgentSpinePlugin({ packageRoot, blunDir, now = new Date().toISOString() }) {
126
- const installedFile = path.join(blunDir, 'plugins', 'installed.json');
127
- ensurePrivateDirectory(path.dirname(installedFile));
128
- if (fs.existsSync(installedFile)) securePrivateFile(installedFile);
129
- const installed = readInstalledPlugins(installedFile);
130
- const records = installed.plugins.filter(plugin => plugin.id === 'agent-spine');
131
- if (records.length > 1) throw new Error('Ambiguous AgentSpine registration');
132
- const selected = selectedAgentSpineRoot({ installed, blunDir });
133
- if (selected) return { installedFile, pluginRoot: selected, reused: true, selected: true };
134
- if (records[0]?.enabled === false) {
135
- return { installedFile, pluginRoot: records[0].root, reused: true, disabled: true };
136
- }
137
- if (records[0]) {
138
- const currentRoot = records[0].root;
139
- if (typeof currentRoot !== 'string' || !path.isAbsolute(currentRoot)) {
140
- throw new Error('Invalid AgentSpine registration root');
141
- }
142
- const managed = path.resolve(blunDir, 'plugins', 'managed');
143
- const sameDirectory = (left, right) => process.platform === 'win32'
144
- ? left.toLowerCase() === right.toLowerCase() : left === right;
145
- if (!sameDirectory(path.dirname(path.resolve(currentRoot)), managed)
146
- || !/^agent-spine-[a-f0-9]{64}$/.test(path.basename(currentRoot))) {
147
- return { installedFile, pluginRoot: currentRoot, reused: true, custom: true };
148
- }
149
- // An upgrade must not conceal changes to the previous immutable default.
150
- describeAgentSpineSelection({ blunDir, pluginRoot: currentRoot });
151
- }
152
- const pluginRoot = copySelectedAgentSpinePlugin({ blunDir, sourceRoot: path.join(packageRoot, 'agent-spine-plugin') });
153
- if (records[0]?.root === pluginRoot) return { installedFile, pluginRoot, reused: true };
154
- writeInstalledPlugins(installedFile, updatedInstalledPlugins(installed, pluginRoot, now, 'agent-spine'));
155
- return { installedFile, pluginRoot, reused: false };
156
- }
157
-
158
- module.exports = {
159
- installManagedAgentSpinePlugin,
160
- installManagedTelegramPlugin,
161
- readInstalledPlugins,
162
- };
1
+ 'use strict';
2
+
3
+ const { randomUUID } = require('node:crypto');
4
+ const fs = require('node:fs');
5
+ const path = require('node:path');
6
+ const { selectedAgentSpineRoot, describeAgentSpineSelection, copySelectedAgentSpinePlugin } = require('./managed-plugin-selection.cjs');
7
+
8
+ const {
9
+ ensurePrivateDirectory,
10
+ securePrivateFile,
11
+ writePrivateFile,
12
+ } = require('./private-paths');
13
+
14
+ function isObject(value) {
15
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
16
+ }
17
+
18
+ function readInstalledPlugins(filePath) {
19
+ if (!fs.existsSync(filePath)) return { version: 1, plugins: [] };
20
+
21
+ const parsed = JSON.parse(fs.readFileSync(filePath, 'utf8'));
22
+ if (!isObject(parsed) || parsed.version !== 1 || !Array.isArray(parsed.plugins)
23
+ || !parsed.plugins.every(isObject)) {
24
+ throw new Error(`Ungültige Plugin-Konfiguration: ${filePath}`);
25
+ }
26
+ return parsed;
27
+ }
28
+
29
+ function updatedInstalledPlugins(installed, pluginRoot, now, pluginId = 'telegram') {
30
+ const previous = installed.plugins.find((plugin) => plugin.id === pluginId);
31
+ const previousCapabilities = isObject(previous?.capabilities) ? previous.capabilities : {};
32
+ const previousMcpServers = isObject(previousCapabilities.mcpServers)
33
+ ? previousCapabilities.mcpServers : {};
34
+ const previousTelegramMcp = isObject(previousMcpServers[pluginId])
35
+ ? previousMcpServers[pluginId] : {};
36
+ const telegram = {
37
+ ...previous,
38
+ id: pluginId,
39
+ root: pluginRoot,
40
+ source: 'local-path',
41
+ enabled: true,
42
+ installedAt: typeof previous?.installedAt === 'string' && previous.installedAt.length > 0
43
+ ? previous.installedAt : now,
44
+ updatedAt: now,
45
+ capabilities: {
46
+ ...previousCapabilities,
47
+ mcpServers: {
48
+ ...previousMcpServers,
49
+ [pluginId]: { ...previousTelegramMcp, enabled: previousTelegramMcp.enabled === false && pluginId === 'agent-spine' ? false : true },
50
+ },
51
+ },
52
+ };
53
+
54
+ const plugins = [];
55
+ let replaced = false;
56
+ for (const plugin of installed.plugins) {
57
+ if (plugin.id !== pluginId) {
58
+ plugins.push(plugin);
59
+ } else if (!replaced) {
60
+ plugins.push(telegram);
61
+ replaced = true;
62
+ }
63
+ }
64
+ if (!replaced) plugins.push(telegram);
65
+ return { ...installed, version: 1, plugins };
66
+ }
67
+
68
+ function writeInstalledPlugins(filePath, installed) {
69
+ const temporaryPath = path.join(
70
+ path.dirname(filePath),
71
+ `.${path.basename(filePath)}.${process.pid}.${randomUUID()}.tmp`,
72
+ );
73
+ try {
74
+ writePrivateFile(temporaryPath, `${JSON.stringify(installed, null, 2)}\n`);
75
+ fs.renameSync(temporaryPath, filePath);
76
+ } catch (error) {
77
+ fs.rmSync(temporaryPath, { force: true });
78
+ throw error;
79
+ }
80
+ }
81
+
82
+ function installManagedTelegramPlugin({ packageRoot, blunDir, now = new Date().toISOString() }) {
83
+ const sourceRoot = path.join(packageRoot, 'telegram-plugin');
84
+ const pluginsDir = path.join(blunDir, 'plugins');
85
+ const managedDir = path.join(pluginsDir, 'managed');
86
+ const pluginRoot = path.join(managedDir, 'telegram');
87
+ const installedFile = path.join(pluginsDir, 'installed.json');
88
+
89
+ ensurePrivateDirectory(pluginsDir);
90
+ if (fs.existsSync(installedFile)) securePrivateFile(installedFile);
91
+ const installed = readInstalledPlugins(installedFile);
92
+ const updatedInstalled = updatedInstalledPlugins(installed, pluginRoot, now);
93
+ ensurePrivateDirectory(managedDir);
94
+ const nonce = `${process.pid}-${randomUUID()}`;
95
+ const stagingRoot = path.join(managedDir, `.telegram-${nonce}.tmp`);
96
+ const backupRoot = path.join(managedDir, `.telegram-${nonce}.bak`);
97
+ let hadPreviousPlugin = false;
98
+ let installedNewPlugin = false;
99
+ try {
100
+ if (fs.existsSync(pluginRoot)) {
101
+ ensurePrivateDirectory(pluginRoot);
102
+ hadPreviousPlugin = true;
103
+ }
104
+ fs.cpSync(sourceRoot, stagingRoot, { recursive: true, force: true });
105
+ if (hadPreviousPlugin) fs.renameSync(pluginRoot, backupRoot);
106
+ try {
107
+ fs.renameSync(stagingRoot, pluginRoot);
108
+ installedNewPlugin = true;
109
+ writeInstalledPlugins(installedFile, updatedInstalled);
110
+ } catch (error) {
111
+ if (installedNewPlugin) fs.rmSync(pluginRoot, { recursive: true, force: true });
112
+ if (hadPreviousPlugin && fs.existsSync(backupRoot)) {
113
+ fs.renameSync(backupRoot, pluginRoot);
114
+ }
115
+ throw error;
116
+ }
117
+ if (hadPreviousPlugin) fs.rmSync(backupRoot, { recursive: true, force: true });
118
+ } finally {
119
+ fs.rmSync(stagingRoot, { recursive: true, force: true });
120
+ }
121
+
122
+ return { installedFile, pluginRoot };
123
+ }
124
+
125
+ function installManagedAgentSpinePlugin({ packageRoot, blunDir, now = new Date().toISOString() }) {
126
+ const installedFile = path.join(blunDir, 'plugins', 'installed.json');
127
+ ensurePrivateDirectory(path.dirname(installedFile));
128
+ if (fs.existsSync(installedFile)) securePrivateFile(installedFile);
129
+ const installed = readInstalledPlugins(installedFile);
130
+ const records = installed.plugins.filter(plugin => plugin.id === 'agent-spine');
131
+ if (records.length > 1) throw new Error('Ambiguous AgentSpine registration');
132
+ const selected = selectedAgentSpineRoot({ installed, blunDir });
133
+ if (selected) return { installedFile, pluginRoot: selected, reused: true, selected: true };
134
+ if (records[0]?.enabled === false) {
135
+ return { installedFile, pluginRoot: records[0].root, reused: true, disabled: true };
136
+ }
137
+ if (records[0]) {
138
+ const currentRoot = records[0].root;
139
+ if (typeof currentRoot !== 'string' || !path.isAbsolute(currentRoot)) {
140
+ throw new Error('Invalid AgentSpine registration root');
141
+ }
142
+ const managed = path.resolve(blunDir, 'plugins', 'managed');
143
+ const sameDirectory = (left, right) => process.platform === 'win32'
144
+ ? left.toLowerCase() === right.toLowerCase() : left === right;
145
+ if (!sameDirectory(path.dirname(path.resolve(currentRoot)), managed)
146
+ || !/^agent-spine-[a-f0-9]{64}$/.test(path.basename(currentRoot))) {
147
+ return { installedFile, pluginRoot: currentRoot, reused: true, custom: true };
148
+ }
149
+ // An upgrade must not conceal changes to the previous immutable default.
150
+ describeAgentSpineSelection({ blunDir, pluginRoot: currentRoot });
151
+ }
152
+ const pluginRoot = copySelectedAgentSpinePlugin({ blunDir, sourceRoot: path.join(packageRoot, 'agent-spine-plugin') });
153
+ if (records[0]?.root === pluginRoot) return { installedFile, pluginRoot, reused: true };
154
+ writeInstalledPlugins(installedFile, updatedInstalledPlugins(installed, pluginRoot, now, 'agent-spine'));
155
+ return { installedFile, pluginRoot, reused: false };
156
+ }
157
+
158
+ module.exports = {
159
+ installManagedAgentSpinePlugin,
160
+ installManagedTelegramPlugin,
161
+ readInstalledPlugins,
162
+ };