blun-king-cli 9.1.601 → 9.1.603

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 +105 -94
  2. package/LIESMICH.txt +57 -57
  3. package/README.md +90 -90
  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 +83 -83
  31. package/bin/windows-node-crash-dump.cjs +289 -289
  32. package/blun.mjs +1709 -1601
  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 +81 -81
  116. package/telegram-plugin/dist/mcp-server.mjs +158 -158
  117. package/worker-host.mjs +932 -932
  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,289 +1,289 @@
1
- 'use strict';
2
-
3
- const { spawnSync } = require('node:child_process');
4
- const fs = require('node:fs');
5
- const path = require('node:path');
6
- const { ensurePrivateDirectory } = require('./private-paths');
7
- const { recordRuntimeExit } = require('./runtime-exit-ledger.cjs');
8
-
9
- const WER_DUMP_COUNT = 3;
10
- const WER_DUMP_TYPE = 1;
11
- const WER_REGISTRY_KEY = 'HKLM\\SOFTWARE\\Microsoft\\Windows\\Windows Error Reporting\\LocalDumps\\node.exe';
12
- const MAX_NODE_REPORTS = 3;
13
- const LEGACY_REPORT_PATTERN = /^blun-node\.[1-9]\d*\.[a-f0-9]{32}\.report\.json$/u;
14
- const REG_OPTIONS = Object.freeze({ encoding: 'utf8', shell: false, windowsHide: true, timeout: 3000, maxBuffer: 65536 });
15
-
16
- function windowsRegistryCommand(options) {
17
- const env = options.env || process.env;
18
- const systemRoot = env.SystemRoot || env.SYSTEMROOT || env.WINDIR || env.windir;
19
- if (typeof systemRoot !== 'string' || !/^[a-z]:\\/iu.test(systemRoot)
20
- || path.win32.normalize(systemRoot) !== systemRoot || systemRoot.slice(2).includes(':')
21
- || /[\u0000-\u001F<>"|?*/]/u.test(systemRoot)
22
- || systemRoot.slice(3).split('\\').some(part => !part || /[. ]$/u.test(part))) {
23
- throw new Error('invalid-system-directory');
24
- }
25
- const directory = path.win32.join(systemRoot, 'System32');
26
- return { executable: path.win32.join(directory, 'reg.exe'),
27
- options: { ...REG_OPTIONS, cwd: directory, env: { SystemRoot: systemRoot, WINDIR: systemRoot } } };
28
- }
29
-
30
- function windowsWerCrashDumpPlan(options = {}) {
31
- const pathImpl = (options.platform || process.platform) === 'win32' ? path.win32 : path;
32
- if (typeof options.homeDir !== 'string' || !pathImpl.isAbsolute(options.homeDir)
33
- || /[\r\n\0]/u.test(options.homeDir)) throw new Error('invalid-home-directory');
34
- const dumpFolder = pathImpl.join(options.homeDir, 'diagnostics', 'crash-dumps');
35
- return { dumpFolder, registryKey: WER_REGISTRY_KEY,
36
- values: { DumpFolder: dumpFolder, DumpCount: WER_DUMP_COUNT, DumpType: WER_DUMP_TYPE } };
37
- }
38
-
39
- function configureWindowsWerCrashDumps(options = {}) {
40
- if ((options.platform || process.platform) !== 'win32') return { configured: false, reason: 'not-windows' };
41
- if (options.confirmSystemWide !== true) return { configured: false, reason: 'explicit-system-wide-consent-required' };
42
- let command;
43
- try { command = windowsRegistryCommand(options); }
44
- catch { return { configured: false, reason: 'system-directory-unavailable' }; }
45
- let plan;
46
- try {
47
- plan = windowsWerCrashDumpPlan({ homeDir: options.homeDir, platform: 'win32' });
48
- (options.prepareDirectory || ensurePrivateDirectory)(plan.dumpFolder);
49
- } catch { return { configured: false, reason: 'report-directory-unavailable' }; }
50
- const execute = options.spawnSync || spawnSync;
51
- const appliedValues = [];
52
- for (const [name, type, value] of [
53
- ['DumpFolder', 'REG_EXPAND_SZ', plan.dumpFolder],
54
- ['DumpCount', 'REG_DWORD', String(WER_DUMP_COUNT)],
55
- ['DumpType', 'REG_DWORD', String(WER_DUMP_TYPE)],
56
- ]) {
57
- let result;
58
- try { result = execute(command.executable, ['ADD', plan.registryKey, '/v', name, '/t', type, '/d', value, '/f'], command.options); }
59
- catch { result = { status: 1 }; }
60
- if (result.error || result.status !== 0) {
61
- return { configured: false, reason: 'registry-write-failed', appliedValues, failedValue: name, plan };
62
- }
63
- appliedValues.push(name);
64
- }
65
- return { configured: true, appliedValues, plan };
66
- }
67
-
68
- function queryWindowsWerCrashDumps(options = {}) {
69
- if ((options.platform || process.platform) !== 'win32') return { configured: false, reason: 'not-windows' };
70
- let plan;
71
- let result;
72
- try {
73
- const command = windowsRegistryCommand(options);
74
- plan = windowsWerCrashDumpPlan({ homeDir: options.homeDir, platform: 'win32' });
75
- result = (options.spawnSync || spawnSync)(command.executable, ['QUERY', plan.registryKey], command.options);
76
- } catch { return { configured: false, reason: 'registry-query-failed' }; }
77
- if (result.error || result.status !== 0) return { configured: false, reason: 'registry-query-failed', plan };
78
- const values = new Map();
79
- for (const line of String(result.stdout || '').split(/\r?\n/u)) {
80
- const match = /^\s+(DumpFolder|DumpCount|DumpType)\s+(REG_EXPAND_SZ|REG_DWORD)\s+(.+?)\s*$/iu.exec(line);
81
- if (match) {
82
- const key = match[1].toLowerCase();
83
- if (values.has(key)) return { configured: false, reason: 'registry-values-mismatch', plan };
84
- values.set(key, [match[2].toUpperCase(), match[3]]);
85
- }
86
- }
87
- const [folderType, folder] = values.get('dumpfolder') || [];
88
- const [countType, count] = values.get('dumpcount') || [];
89
- const [dumpType, type] = values.get('dumptype') || [];
90
- const configured = folderType === 'REG_EXPAND_SZ'
91
- && path.win32.normalize(folder).toLowerCase() === path.win32.normalize(plan.dumpFolder).toLowerCase()
92
- && countType === 'REG_DWORD' && /^0x0*3$/iu.test(count)
93
- && dumpType === 'REG_DWORD' && /^0x0*1$/iu.test(type);
94
- return { configured, reason: configured ? undefined : 'registry-values-mismatch', plan };
95
- }
96
-
97
- function reportFailure(code) {
98
- return Object.assign(new Error(code), { reportFailure: code });
99
- }
100
-
101
- function sameEntry(left, right) {
102
- return left.dev === right.dev && left.ino === right.ino;
103
- }
104
-
105
- function reportIdentity(file) {
106
- try {
107
- const stat = fs.lstatSync(file, { bigint: true });
108
- if (!stat.isFile() || stat.isSymbolicLink() || stat.nlink !== 1n) throw reportFailure('unsafe-report-file');
109
- return stat;
110
- } catch (error) {
111
- if (error.code === 'ENOENT') return undefined;
112
- throw error;
113
- }
114
- }
115
-
116
- function reserveReportSlot(directory, pid) {
117
- const identity = fs.lstatSync(directory, { bigint: true });
118
- const verifyDirectory = () => {
119
- const current = fs.lstatSync(directory, { bigint: true });
120
- if (!current.isDirectory() || current.isSymbolicLink() || !sameEntry(current, identity)) {
121
- throw reportFailure('unsafe-report-directory');
122
- }
123
- };
124
- verifyDirectory();
125
- const entries = fs.opendirSync(directory);
126
- try {
127
- let count = 0;
128
- for (let entry = entries.readSync(); entry; entry = entries.readSync()) {
129
- if (++count > 1024) throw reportFailure('report-directory-too-large');
130
- if (LEGACY_REPORT_PATTERN.test(entry.name)) throw reportFailure('legacy-reports-require-cleanup');
131
- }
132
- } finally { entries.closeSync(); }
133
-
134
- const slots = Array.from({ length: MAX_NODE_REPORTS }, (_, index) => {
135
- const filename = `blun-node.slot-${index + 1}.report.json`;
136
- return { filename, lock: path.join(directory, `blun-node.slot-${index + 1}.lock`),
137
- previous: reportIdentity(path.join(directory, filename)) };
138
- }).toSorted((left, right) => {
139
- const a = left.previous?.mtimeNs ?? -1n;
140
- const b = right.previous?.mtimeNs ?? -1n;
141
- return a < b ? -1 : a > b ? 1 : 0;
142
- });
143
- for (const slot of slots) {
144
- verifyDirectory();
145
- let descriptor;
146
- try { descriptor = fs.openSync(slot.lock, 'wx', 0o600); }
147
- catch (error) { if (error.code === 'EEXIST') continue; throw error; }
148
- let lockIdentity;
149
- let closed = false;
150
- const release = () => {
151
- if (closed) return;
152
- closed = true;
153
- try {
154
- verifyDirectory();
155
- const current = reportIdentity(slot.lock);
156
- if (lockIdentity && current && sameEntry(current, lockIdentity)
157
- && sameEntry(fs.fstatSync(descriptor, { bigint: true }), lockIdentity)) fs.unlinkSync(slot.lock);
158
- } catch {} finally { try { fs.closeSync(descriptor); } catch {} }
159
- };
160
- try {
161
- lockIdentity = fs.fstatSync(descriptor, { bigint: true });
162
- if (!lockIdentity.isFile() || lockIdentity.nlink !== 1n) throw reportFailure('unsafe-report-reservation');
163
- fs.writeFileSync(descriptor, `${JSON.stringify({ schemaVersion: 1, pid })}\n`);
164
- verifyDirectory();
165
- const reportPath = path.join(directory, slot.filename);
166
- const previous = reportIdentity(reportPath);
167
- return { filename: slot.filename, release,
168
- verify() {
169
- verifyDirectory();
170
- const owner = reportIdentity(slot.lock);
171
- const current = reportIdentity(reportPath);
172
- if (!owner || !sameEntry(owner, lockIdentity)
173
- || (previous ? !current || !sameEntry(previous, current)
174
- || previous.size !== current.size || previous.mtimeNs !== current.mtimeNs : current !== undefined)) {
175
- throw reportFailure('report-file-changed');
176
- }
177
- } };
178
- } catch (error) { release(); throw error; }
179
- }
180
- // Abrupt native exits may leave reservations behind. Never guess that an
181
- // owner is dead: capacity exhaustion disables detail reports, not startup.
182
- throw reportFailure('report-capacity-unavailable');
183
- }
184
-
185
- function installWindowsNodeCrashDump(options = {}) {
186
- if ((options.platform || process.platform) !== 'win32') {
187
- return { enabled: false, detailedReportsEnabled: false, reason: 'not-windows', dispose() {} };
188
- }
189
- const processObject = options.processObject || process;
190
- const report = options.report || processObject.report;
191
- const homeDir = options.homeDir || processObject.env.BLUN_HOME;
192
- const env = options.env || processObject.env;
193
- const recordExit = options.recordExit || recordRuntimeExit;
194
- const requested = env.BLUN_NODE_CRASH_REPORTS === '1';
195
- let detailedReportsEnabled = false;
196
- let dumpFolder;
197
- let reason;
198
- let previous;
199
- let assigned;
200
- let filename;
201
- let reservation;
202
- const restore = () => {
203
- if (!previous) return;
204
- for (const key of Object.keys(previous)) {
205
- try { if (report[key] === assigned[key]) report[key] = previous[key]; } catch {}
206
- }
207
- };
208
- const releaseReservation = () => {
209
- try {
210
- if (!assigned || report.directory !== dumpFolder || report.filename !== filename
211
- || !(report.reportOnFatalError || report.reportOnUncaughtException || report.reportOnSignal)) reservation?.release();
212
- } catch {}
213
- };
214
- if (requested) {
215
- if (!report || typeof report.writeReport !== 'function' || !('excludeEnv' in report)
216
- || !('excludeNetwork' in report)) reason = 'report-unavailable';
217
- else {
218
- try {
219
- if (typeof homeDir !== 'string' || !path.isAbsolute(homeDir)) throw new Error('invalid-home-directory');
220
- dumpFolder = path.join(homeDir, 'diagnostics', 'crash-dumps');
221
- (options.prepareDirectory || ensurePrivateDirectory)(dumpFolder);
222
- reservation = reserveReportSlot(dumpFolder, processObject.pid);
223
- filename = reservation.filename;
224
- assigned = { directory: dumpFolder, filename, excludeEnv: true, excludeNetwork: true,
225
- reportOnSignal: false, reportOnUncaughtException: false, reportOnFatalError: true };
226
- previous = Object.fromEntries(Object.keys(assigned).map(key => [key, report[key]]));
227
- for (const [key, value] of Object.entries(assigned)) {
228
- report[key] = value;
229
- if (report[key] !== value) throw new Error('report-setting-rejected');
230
- }
231
- detailedReportsEnabled = true;
232
- } catch (error) {
233
- restore();
234
- releaseReservation();
235
- reason = error.reportFailure || 'report-directory-unavailable';
236
- }
237
- }
238
- }
239
- let attempted = false;
240
- let disposed = false;
241
- const write = (error, origin) => {
242
- if (disposed || attempted) return null;
243
- attempted = true;
244
- const kind = origin === 'bootstrap-failure' ? origin
245
- : origin === 'unhandledRejection' ? 'unhandled-rejection' : 'uncaught-exception';
246
- try { recordExit({ homeDir, source: 'bootstrap', kind, error, pid: processObject.pid,
247
- ppid: processObject.ppid, phase: kind === 'bootstrap-failure' ? 'startup' : 'runtime' }); } catch {}
248
- if (!detailedReportsEnabled) return null;
249
- try {
250
- if (report.directory !== dumpFolder || report.filename !== filename
251
- || report.excludeEnv !== true || report.excludeNetwork !== true) return null;
252
- reservation.verify();
253
- report.writeReport(filename, error instanceof Error ? error : new Error(String(error)));
254
- return path.join(dumpFolder, filename);
255
- } catch { return null; }
256
- };
257
- const dispose = () => {
258
- if (disposed) return;
259
- disposed = true;
260
- try { processObject.removeListener('uncaughtExceptionMonitor', write); } catch {}
261
- try { processObject.removeListener('exit', dispose); } catch {}
262
- restore();
263
- releaseReservation();
264
- };
265
- try {
266
- processObject.on('uncaughtExceptionMonitor', write);
267
- processObject.once('exit', dispose);
268
- } catch {
269
- dispose();
270
- return { enabled: false, detailedReportsEnabled: false, reason: 'observer-unavailable', write, dispose };
271
- }
272
- return { enabled: true, detailedReportsEnabled, dumpFolder, reason, write, dispose };
273
- }
274
-
275
- if (require.main === module) {
276
- const args = process.argv.slice(2);
277
- let result;
278
- if (args.length === 1 && args[0] === '--query-wer') result = queryWindowsWerCrashDumps({ homeDir: process.env.BLUN_HOME });
279
- else if (args.length === 2 && args[0] === '--configure-wer' && args[1] === '--confirm-system-wide') {
280
- result = configureWindowsWerCrashDumps({ homeDir: process.env.BLUN_HOME, confirmSystemWide: true });
281
- } else {
282
- result = { configured: false, reason: 'explicit-command-required' };
283
- }
284
- process.stdout.write(`${JSON.stringify(result)}\n`);
285
- process.exitCode = result.configured ? 0 : 1;
286
- }
287
-
288
- module.exports = { WER_DUMP_COUNT, WER_DUMP_TYPE, MAX_NODE_REPORTS, configureWindowsWerCrashDumps,
289
- installWindowsNodeCrashDump, queryWindowsWerCrashDumps, windowsWerCrashDumpPlan };
1
+ 'use strict';
2
+
3
+ const { spawnSync } = require('node:child_process');
4
+ const fs = require('node:fs');
5
+ const path = require('node:path');
6
+ const { ensurePrivateDirectory } = require('./private-paths');
7
+ const { recordRuntimeExit } = require('./runtime-exit-ledger.cjs');
8
+
9
+ const WER_DUMP_COUNT = 3;
10
+ const WER_DUMP_TYPE = 1;
11
+ const WER_REGISTRY_KEY = 'HKLM\\SOFTWARE\\Microsoft\\Windows\\Windows Error Reporting\\LocalDumps\\node.exe';
12
+ const MAX_NODE_REPORTS = 3;
13
+ const LEGACY_REPORT_PATTERN = /^blun-node\.[1-9]\d*\.[a-f0-9]{32}\.report\.json$/u;
14
+ const REG_OPTIONS = Object.freeze({ encoding: 'utf8', shell: false, windowsHide: true, timeout: 3000, maxBuffer: 65536 });
15
+
16
+ function windowsRegistryCommand(options) {
17
+ const env = options.env || process.env;
18
+ const systemRoot = env.SystemRoot || env.SYSTEMROOT || env.WINDIR || env.windir;
19
+ if (typeof systemRoot !== 'string' || !/^[a-z]:\\/iu.test(systemRoot)
20
+ || path.win32.normalize(systemRoot) !== systemRoot || systemRoot.slice(2).includes(':')
21
+ || /[\u0000-\u001F<>"|?*/]/u.test(systemRoot)
22
+ || systemRoot.slice(3).split('\\').some(part => !part || /[. ]$/u.test(part))) {
23
+ throw new Error('invalid-system-directory');
24
+ }
25
+ const directory = path.win32.join(systemRoot, 'System32');
26
+ return { executable: path.win32.join(directory, 'reg.exe'),
27
+ options: { ...REG_OPTIONS, cwd: directory, env: { SystemRoot: systemRoot, WINDIR: systemRoot } } };
28
+ }
29
+
30
+ function windowsWerCrashDumpPlan(options = {}) {
31
+ const pathImpl = (options.platform || process.platform) === 'win32' ? path.win32 : path;
32
+ if (typeof options.homeDir !== 'string' || !pathImpl.isAbsolute(options.homeDir)
33
+ || /[\r\n\0]/u.test(options.homeDir)) throw new Error('invalid-home-directory');
34
+ const dumpFolder = pathImpl.join(options.homeDir, 'diagnostics', 'crash-dumps');
35
+ return { dumpFolder, registryKey: WER_REGISTRY_KEY,
36
+ values: { DumpFolder: dumpFolder, DumpCount: WER_DUMP_COUNT, DumpType: WER_DUMP_TYPE } };
37
+ }
38
+
39
+ function configureWindowsWerCrashDumps(options = {}) {
40
+ if ((options.platform || process.platform) !== 'win32') return { configured: false, reason: 'not-windows' };
41
+ if (options.confirmSystemWide !== true) return { configured: false, reason: 'explicit-system-wide-consent-required' };
42
+ let command;
43
+ try { command = windowsRegistryCommand(options); }
44
+ catch { return { configured: false, reason: 'system-directory-unavailable' }; }
45
+ let plan;
46
+ try {
47
+ plan = windowsWerCrashDumpPlan({ homeDir: options.homeDir, platform: 'win32' });
48
+ (options.prepareDirectory || ensurePrivateDirectory)(plan.dumpFolder);
49
+ } catch { return { configured: false, reason: 'report-directory-unavailable' }; }
50
+ const execute = options.spawnSync || spawnSync;
51
+ const appliedValues = [];
52
+ for (const [name, type, value] of [
53
+ ['DumpFolder', 'REG_EXPAND_SZ', plan.dumpFolder],
54
+ ['DumpCount', 'REG_DWORD', String(WER_DUMP_COUNT)],
55
+ ['DumpType', 'REG_DWORD', String(WER_DUMP_TYPE)],
56
+ ]) {
57
+ let result;
58
+ try { result = execute(command.executable, ['ADD', plan.registryKey, '/v', name, '/t', type, '/d', value, '/f'], command.options); }
59
+ catch { result = { status: 1 }; }
60
+ if (result.error || result.status !== 0) {
61
+ return { configured: false, reason: 'registry-write-failed', appliedValues, failedValue: name, plan };
62
+ }
63
+ appliedValues.push(name);
64
+ }
65
+ return { configured: true, appliedValues, plan };
66
+ }
67
+
68
+ function queryWindowsWerCrashDumps(options = {}) {
69
+ if ((options.platform || process.platform) !== 'win32') return { configured: false, reason: 'not-windows' };
70
+ let plan;
71
+ let result;
72
+ try {
73
+ const command = windowsRegistryCommand(options);
74
+ plan = windowsWerCrashDumpPlan({ homeDir: options.homeDir, platform: 'win32' });
75
+ result = (options.spawnSync || spawnSync)(command.executable, ['QUERY', plan.registryKey], command.options);
76
+ } catch { return { configured: false, reason: 'registry-query-failed' }; }
77
+ if (result.error || result.status !== 0) return { configured: false, reason: 'registry-query-failed', plan };
78
+ const values = new Map();
79
+ for (const line of String(result.stdout || '').split(/\r?\n/u)) {
80
+ const match = /^\s+(DumpFolder|DumpCount|DumpType)\s+(REG_EXPAND_SZ|REG_DWORD)\s+(.+?)\s*$/iu.exec(line);
81
+ if (match) {
82
+ const key = match[1].toLowerCase();
83
+ if (values.has(key)) return { configured: false, reason: 'registry-values-mismatch', plan };
84
+ values.set(key, [match[2].toUpperCase(), match[3]]);
85
+ }
86
+ }
87
+ const [folderType, folder] = values.get('dumpfolder') || [];
88
+ const [countType, count] = values.get('dumpcount') || [];
89
+ const [dumpType, type] = values.get('dumptype') || [];
90
+ const configured = folderType === 'REG_EXPAND_SZ'
91
+ && path.win32.normalize(folder).toLowerCase() === path.win32.normalize(plan.dumpFolder).toLowerCase()
92
+ && countType === 'REG_DWORD' && /^0x0*3$/iu.test(count)
93
+ && dumpType === 'REG_DWORD' && /^0x0*1$/iu.test(type);
94
+ return { configured, reason: configured ? undefined : 'registry-values-mismatch', plan };
95
+ }
96
+
97
+ function reportFailure(code) {
98
+ return Object.assign(new Error(code), { reportFailure: code });
99
+ }
100
+
101
+ function sameEntry(left, right) {
102
+ return left.dev === right.dev && left.ino === right.ino;
103
+ }
104
+
105
+ function reportIdentity(file) {
106
+ try {
107
+ const stat = fs.lstatSync(file, { bigint: true });
108
+ if (!stat.isFile() || stat.isSymbolicLink() || stat.nlink !== 1n) throw reportFailure('unsafe-report-file');
109
+ return stat;
110
+ } catch (error) {
111
+ if (error.code === 'ENOENT') return undefined;
112
+ throw error;
113
+ }
114
+ }
115
+
116
+ function reserveReportSlot(directory, pid) {
117
+ const identity = fs.lstatSync(directory, { bigint: true });
118
+ const verifyDirectory = () => {
119
+ const current = fs.lstatSync(directory, { bigint: true });
120
+ if (!current.isDirectory() || current.isSymbolicLink() || !sameEntry(current, identity)) {
121
+ throw reportFailure('unsafe-report-directory');
122
+ }
123
+ };
124
+ verifyDirectory();
125
+ const entries = fs.opendirSync(directory);
126
+ try {
127
+ let count = 0;
128
+ for (let entry = entries.readSync(); entry; entry = entries.readSync()) {
129
+ if (++count > 1024) throw reportFailure('report-directory-too-large');
130
+ if (LEGACY_REPORT_PATTERN.test(entry.name)) throw reportFailure('legacy-reports-require-cleanup');
131
+ }
132
+ } finally { entries.closeSync(); }
133
+
134
+ const slots = Array.from({ length: MAX_NODE_REPORTS }, (_, index) => {
135
+ const filename = `blun-node.slot-${index + 1}.report.json`;
136
+ return { filename, lock: path.join(directory, `blun-node.slot-${index + 1}.lock`),
137
+ previous: reportIdentity(path.join(directory, filename)) };
138
+ }).toSorted((left, right) => {
139
+ const a = left.previous?.mtimeNs ?? -1n;
140
+ const b = right.previous?.mtimeNs ?? -1n;
141
+ return a < b ? -1 : a > b ? 1 : 0;
142
+ });
143
+ for (const slot of slots) {
144
+ verifyDirectory();
145
+ let descriptor;
146
+ try { descriptor = fs.openSync(slot.lock, 'wx', 0o600); }
147
+ catch (error) { if (error.code === 'EEXIST') continue; throw error; }
148
+ let lockIdentity;
149
+ let closed = false;
150
+ const release = () => {
151
+ if (closed) return;
152
+ closed = true;
153
+ try {
154
+ verifyDirectory();
155
+ const current = reportIdentity(slot.lock);
156
+ if (lockIdentity && current && sameEntry(current, lockIdentity)
157
+ && sameEntry(fs.fstatSync(descriptor, { bigint: true }), lockIdentity)) fs.unlinkSync(slot.lock);
158
+ } catch {} finally { try { fs.closeSync(descriptor); } catch {} }
159
+ };
160
+ try {
161
+ lockIdentity = fs.fstatSync(descriptor, { bigint: true });
162
+ if (!lockIdentity.isFile() || lockIdentity.nlink !== 1n) throw reportFailure('unsafe-report-reservation');
163
+ fs.writeFileSync(descriptor, `${JSON.stringify({ schemaVersion: 1, pid })}\n`);
164
+ verifyDirectory();
165
+ const reportPath = path.join(directory, slot.filename);
166
+ const previous = reportIdentity(reportPath);
167
+ return { filename: slot.filename, release,
168
+ verify() {
169
+ verifyDirectory();
170
+ const owner = reportIdentity(slot.lock);
171
+ const current = reportIdentity(reportPath);
172
+ if (!owner || !sameEntry(owner, lockIdentity)
173
+ || (previous ? !current || !sameEntry(previous, current)
174
+ || previous.size !== current.size || previous.mtimeNs !== current.mtimeNs : current !== undefined)) {
175
+ throw reportFailure('report-file-changed');
176
+ }
177
+ } };
178
+ } catch (error) { release(); throw error; }
179
+ }
180
+ // Abrupt native exits may leave reservations behind. Never guess that an
181
+ // owner is dead: capacity exhaustion disables detail reports, not startup.
182
+ throw reportFailure('report-capacity-unavailable');
183
+ }
184
+
185
+ function installWindowsNodeCrashDump(options = {}) {
186
+ if ((options.platform || process.platform) !== 'win32') {
187
+ return { enabled: false, detailedReportsEnabled: false, reason: 'not-windows', dispose() {} };
188
+ }
189
+ const processObject = options.processObject || process;
190
+ const report = options.report || processObject.report;
191
+ const homeDir = options.homeDir || processObject.env.BLUN_HOME;
192
+ const env = options.env || processObject.env;
193
+ const recordExit = options.recordExit || recordRuntimeExit;
194
+ const requested = env.BLUN_NODE_CRASH_REPORTS === '1';
195
+ let detailedReportsEnabled = false;
196
+ let dumpFolder;
197
+ let reason;
198
+ let previous;
199
+ let assigned;
200
+ let filename;
201
+ let reservation;
202
+ const restore = () => {
203
+ if (!previous) return;
204
+ for (const key of Object.keys(previous)) {
205
+ try { if (report[key] === assigned[key]) report[key] = previous[key]; } catch {}
206
+ }
207
+ };
208
+ const releaseReservation = () => {
209
+ try {
210
+ if (!assigned || report.directory !== dumpFolder || report.filename !== filename
211
+ || !(report.reportOnFatalError || report.reportOnUncaughtException || report.reportOnSignal)) reservation?.release();
212
+ } catch {}
213
+ };
214
+ if (requested) {
215
+ if (!report || typeof report.writeReport !== 'function' || !('excludeEnv' in report)
216
+ || !('excludeNetwork' in report)) reason = 'report-unavailable';
217
+ else {
218
+ try {
219
+ if (typeof homeDir !== 'string' || !path.isAbsolute(homeDir)) throw new Error('invalid-home-directory');
220
+ dumpFolder = path.join(homeDir, 'diagnostics', 'crash-dumps');
221
+ (options.prepareDirectory || ensurePrivateDirectory)(dumpFolder);
222
+ reservation = reserveReportSlot(dumpFolder, processObject.pid);
223
+ filename = reservation.filename;
224
+ assigned = { directory: dumpFolder, filename, excludeEnv: true, excludeNetwork: true,
225
+ reportOnSignal: false, reportOnUncaughtException: false, reportOnFatalError: true };
226
+ previous = Object.fromEntries(Object.keys(assigned).map(key => [key, report[key]]));
227
+ for (const [key, value] of Object.entries(assigned)) {
228
+ report[key] = value;
229
+ if (report[key] !== value) throw new Error('report-setting-rejected');
230
+ }
231
+ detailedReportsEnabled = true;
232
+ } catch (error) {
233
+ restore();
234
+ releaseReservation();
235
+ reason = error.reportFailure || 'report-directory-unavailable';
236
+ }
237
+ }
238
+ }
239
+ let attempted = false;
240
+ let disposed = false;
241
+ const write = (error, origin) => {
242
+ if (disposed || attempted) return null;
243
+ attempted = true;
244
+ const kind = origin === 'bootstrap-failure' ? origin
245
+ : origin === 'unhandledRejection' ? 'unhandled-rejection' : 'uncaught-exception';
246
+ try { recordExit({ homeDir, source: 'bootstrap', kind, error, pid: processObject.pid,
247
+ ppid: processObject.ppid, phase: kind === 'bootstrap-failure' ? 'startup' : 'runtime' }); } catch {}
248
+ if (!detailedReportsEnabled) return null;
249
+ try {
250
+ if (report.directory !== dumpFolder || report.filename !== filename
251
+ || report.excludeEnv !== true || report.excludeNetwork !== true) return null;
252
+ reservation.verify();
253
+ report.writeReport(filename, error instanceof Error ? error : new Error(String(error)));
254
+ return path.join(dumpFolder, filename);
255
+ } catch { return null; }
256
+ };
257
+ const dispose = () => {
258
+ if (disposed) return;
259
+ disposed = true;
260
+ try { processObject.removeListener('uncaughtExceptionMonitor', write); } catch {}
261
+ try { processObject.removeListener('exit', dispose); } catch {}
262
+ restore();
263
+ releaseReservation();
264
+ };
265
+ try {
266
+ processObject.on('uncaughtExceptionMonitor', write);
267
+ processObject.once('exit', dispose);
268
+ } catch {
269
+ dispose();
270
+ return { enabled: false, detailedReportsEnabled: false, reason: 'observer-unavailable', write, dispose };
271
+ }
272
+ return { enabled: true, detailedReportsEnabled, dumpFolder, reason, write, dispose };
273
+ }
274
+
275
+ if (require.main === module) {
276
+ const args = process.argv.slice(2);
277
+ let result;
278
+ if (args.length === 1 && args[0] === '--query-wer') result = queryWindowsWerCrashDumps({ homeDir: process.env.BLUN_HOME });
279
+ else if (args.length === 2 && args[0] === '--configure-wer' && args[1] === '--confirm-system-wide') {
280
+ result = configureWindowsWerCrashDumps({ homeDir: process.env.BLUN_HOME, confirmSystemWide: true });
281
+ } else {
282
+ result = { configured: false, reason: 'explicit-command-required' };
283
+ }
284
+ process.stdout.write(`${JSON.stringify(result)}\n`);
285
+ process.exitCode = result.configured ? 0 : 1;
286
+ }
287
+
288
+ module.exports = { WER_DUMP_COUNT, WER_DUMP_TYPE, MAX_NODE_REPORTS, configureWindowsWerCrashDumps,
289
+ installWindowsNodeCrashDump, queryWindowsWerCrashDumps, windowsWerCrashDumpPlan };