blun-king-cli 9.1.623 → 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 -209
  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 +194198 -189561
  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 +13879 -17458
  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,144 +1,144 @@
1
- 'use strict';
2
-
3
- const fs = require('node:fs');
4
- const os = require('node:os');
5
- const path = require('node:path');
6
-
7
- const DEFAULT_MAX_BYTES = 1024 * 1024;
8
- const DEFAULT_RETAIN_BYTES = 512 * 1024;
9
-
10
- function runtimeExitLedgerPath(homeDir) {
11
- return path.join(homeDir, 'diagnostics', 'runtime-exits.jsonl');
12
- }
13
-
14
- function sanitizeExitText(input, maxChars = 2048) {
15
- let value = String(input ?? '');
16
- const homes = new Set([os.homedir(), process.env.USERPROFILE, process.env.HOME]
17
- .filter((entry) => typeof entry === 'string' && entry.length > 2));
18
- for (const home of homes) {
19
- for (const variant of [path.normalize(home), home.replaceAll('\\', '/')]) {
20
- const escaped = variant.replace(/[.*+?^${}()|[\]\\]/gu, '\\$&');
21
- value = value.replace(new RegExp(escaped, process.platform === 'win32' ? 'giu' : 'gu'), '$HOME');
22
- }
23
- }
24
- value = value
25
- .replace(/[A-Za-z]:[\\/]Users[\\/][^\\/\s"']+/giu, '$HOME')
26
- .replace(/\/home\/[^/\s"']+/gu, '$HOME')
27
- .replace(/(Authorization\s*:\s*Bearer\s+)[^\s,;]+/giu, '$1<redacted>')
28
- .replace(/\bsk-[A-Za-z0-9_-]{10,}\b/gu, '<redacted>')
29
- .replace(/\b\d{6,}:[A-Za-z0-9_-]{20,}\b/gu, '<redacted>')
30
- .replace(/("(?:api[_-]?key|access[_-]?token|refresh[_-]?token|token|secret|password)"\s*:\s*)"(?:\\.|[^"\\])*"/giu, '$1"<redacted>"')
31
- .replace(/((?:api[_-]?key|access[_-]?token|refresh[_-]?token|token|secret|password)\s*[=:]\s*)[^\s,;}]+/giu, '$1<redacted>')
32
- .replace(/(https?:\/\/[^\s/:@]+:)[^\s/@]+@/giu, '$1<redacted>@');
33
- return value.length <= maxChars ? value : `${value.slice(0, maxChars)}...[truncated]`;
34
- }
35
-
36
- function buildRuntimeExitRecord(input) {
37
- const now = typeof input.now === 'function' ? input.now() : new Date();
38
- const record = { schemaVersion: 1, ts: new Date(now).toISOString() };
39
- for (const key of ['source', 'kind', 'signal', 'phase', 'cliVersion', 'profile', 'sessionId', 'lastTool', 'detail']) {
40
- if (input[key] !== undefined && input[key] !== '') record[key] = sanitizeExitText(input[key]);
41
- }
42
- for (const [key, value] of Object.entries({ exitCode: input.exitCode, pid: input.pid ?? process.pid,
43
- ppid: input.ppid ?? process.ppid, childPid: input.childPid })) {
44
- if (Number.isInteger(value)) record[key] = value;
45
- }
46
- if (typeof input.loaded === 'boolean') record.loaded = input.loaded;
47
- if (input.error !== undefined && input.error !== null) {
48
- record.error = {
49
- name: sanitizeExitText(input.error.name || 'Error', 160),
50
- message: sanitizeExitText(input.error.message || input.error),
51
- stack: sanitizeExitText(input.error.stack || '', 8192),
52
- };
53
- }
54
- return record;
55
- }
56
-
57
- function directoryIdentity(directory) {
58
- const stat = fs.lstatSync(directory, { bigint: true });
59
- if (!stat.isDirectory() || stat.isSymbolicLink()) throw new Error('Unsafe exit directory');
60
- return stat;
61
- }
62
-
63
- function sameIdentity(left, right) {
64
- return left.dev === right.dev && left.ino === right.ino;
65
- }
66
-
67
- function regularLedger(stat) {
68
- if (!stat.isFile() || stat.isSymbolicLink() || stat.nlink !== 1n) throw new Error('Unsafe exit ledger');
69
- return stat;
70
- }
71
-
72
- function trimLedger(descriptor, maxBytes, retainBytes, incomingBytes) {
73
- const size = Number(regularLedger(fs.fstatSync(descriptor, { bigint: true })).size);
74
- if (!Number.isSafeInteger(size)) throw new Error('Invalid exit ledger size');
75
- if (size + incomingBytes <= maxBytes) return;
76
- const keepBytes = Math.max(0, Math.min(size, retainBytes, maxBytes - incomingBytes));
77
- const contents = Buffer.alloc(keepBytes);
78
- const read = fs.readSync(descriptor, contents, 0, keepBytes, size - keepBytes);
79
- const tail = contents.subarray(0, read);
80
- const lineStart = size > keepBytes ? tail.indexOf(0x0a) + 1 : 0;
81
- fs.ftruncateSync(descriptor, 0);
82
- fs.writeFileSync(descriptor, lineStart > 0 || size === keepBytes ? tail.subarray(lineStart) : '');
83
- }
84
-
85
- function recordRuntimeExit(input = {}) {
86
- let descriptor;
87
- let appendDescriptor;
88
- try {
89
- const homeDir = typeof input.homeDir === 'string' ? input.homeDir.trim() : '';
90
- if (!homeDir || !path.isAbsolute(homeDir)) return false;
91
- // Pin the private home and diagnostic directory before opening any output.
92
- fs.mkdirSync(homeDir, { recursive: true, mode: 0o700 });
93
- const homeIdentity = directoryIdentity(homeDir);
94
- const file = runtimeExitLedgerPath(homeDir);
95
- const directory = path.dirname(file);
96
- try { fs.mkdirSync(directory, { mode: 0o700 }); }
97
- catch (error) { if (error.code !== 'EEXIST') throw error; }
98
- const directoryStat = directoryIdentity(directory);
99
- let before;
100
- try { before = regularLedger(fs.lstatSync(file, { bigint: true })); }
101
- catch (error) { if (error.code !== 'ENOENT') throw error; }
102
- const flags = fs.constants.O_RDWR | (fs.constants.O_NOFOLLOW ?? 0)
103
- | (fs.constants.O_NONBLOCK ?? 0) | (before ? 0 : fs.constants.O_CREAT | fs.constants.O_EXCL);
104
- descriptor = fs.openSync(file, flags, 0o600);
105
- const opened = regularLedger(fs.fstatSync(descriptor, { bigint: true }));
106
- if ((before && !sameIdentity(before, opened))
107
- || !sameIdentity(opened, regularLedger(fs.lstatSync(file, { bigint: true })))
108
- || !sameIdentity(homeIdentity, directoryIdentity(homeDir))
109
- || !sameIdentity(directoryStat, directoryIdentity(directory))) throw new Error('Exit ledger changed');
110
- const maxBytes = Math.max(1024, Number.isInteger(input.maxBytes) ? input.maxBytes : DEFAULT_MAX_BYTES);
111
- const retainBytes = Math.max(0, Number.isInteger(input.retainBytes) ? input.retainBytes : DEFAULT_RETAIN_BYTES);
112
- const record = buildRuntimeExitRecord(input);
113
- let line = `${JSON.stringify(record)}\n`;
114
- // Retain identity/exit fields even when a diagnostic detail exceeds the cap.
115
- for (const key of ['error', 'detail', 'lastTool', 'sessionId', 'profile', 'cliVersion', 'phase', 'source', 'kind', 'signal']) {
116
- if (Buffer.byteLength(line) <= maxBytes) break;
117
- delete record[key];
118
- record.truncated = true;
119
- line = `${JSON.stringify(record)}\n`;
120
- }
121
- trimLedger(descriptor, maxBytes, retainBytes, Buffer.byteLength(line));
122
- // Windows append-only handles cannot truncate. Keep both handles bound to
123
- // the same checked inode, using the second handle only for atomic append.
124
- appendDescriptor = fs.openSync(file, fs.constants.O_WRONLY | fs.constants.O_APPEND
125
- | (fs.constants.O_NOFOLLOW ?? 0) | (fs.constants.O_NONBLOCK ?? 0));
126
- if (!sameIdentity(opened, regularLedger(fs.fstatSync(appendDescriptor, { bigint: true })))
127
- || !sameIdentity(opened, regularLedger(fs.lstatSync(file, { bigint: true })))
128
- || !sameIdentity(homeIdentity, directoryIdentity(homeDir))
129
- || !sameIdentity(directoryStat, directoryIdentity(directory))) throw new Error('Exit ledger changed');
130
- fs.appendFileSync(appendDescriptor, line, { encoding: 'utf8' });
131
- try { fs.fchmodSync(descriptor, 0o600); } catch {}
132
- return true;
133
- } catch {
134
- return false;
135
- } finally {
136
- if (appendDescriptor !== undefined) { try { fs.closeSync(appendDescriptor); } catch {} }
137
- if (descriptor !== undefined) { try { fs.closeSync(descriptor); } catch {} }
138
- }
139
- }
140
-
141
- module.exports = {
142
- DEFAULT_MAX_BYTES, DEFAULT_RETAIN_BYTES, buildRuntimeExitRecord, recordRuntimeExit,
143
- runtimeExitLedgerPath, sanitizeExitText,
144
- };
1
+ 'use strict';
2
+
3
+ const fs = require('node:fs');
4
+ const os = require('node:os');
5
+ const path = require('node:path');
6
+
7
+ const DEFAULT_MAX_BYTES = 1024 * 1024;
8
+ const DEFAULT_RETAIN_BYTES = 512 * 1024;
9
+
10
+ function runtimeExitLedgerPath(homeDir) {
11
+ return path.join(homeDir, 'diagnostics', 'runtime-exits.jsonl');
12
+ }
13
+
14
+ function sanitizeExitText(input, maxChars = 2048) {
15
+ let value = String(input ?? '');
16
+ const homes = new Set([os.homedir(), process.env.USERPROFILE, process.env.HOME]
17
+ .filter((entry) => typeof entry === 'string' && entry.length > 2));
18
+ for (const home of homes) {
19
+ for (const variant of [path.normalize(home), home.replaceAll('\\', '/')]) {
20
+ const escaped = variant.replace(/[.*+?^${}()|[\]\\]/gu, '\\$&');
21
+ value = value.replace(new RegExp(escaped, process.platform === 'win32' ? 'giu' : 'gu'), '$HOME');
22
+ }
23
+ }
24
+ value = value
25
+ .replace(/[A-Za-z]:[\\/]Users[\\/][^\\/\s"']+/giu, '$HOME')
26
+ .replace(/\/home\/[^/\s"']+/gu, '$HOME')
27
+ .replace(/(Authorization\s*:\s*Bearer\s+)[^\s,;]+/giu, '$1<redacted>')
28
+ .replace(/\bsk-[A-Za-z0-9_-]{10,}\b/gu, '<redacted>')
29
+ .replace(/\b\d{6,}:[A-Za-z0-9_-]{20,}\b/gu, '<redacted>')
30
+ .replace(/("(?:api[_-]?key|access[_-]?token|refresh[_-]?token|token|secret|password)"\s*:\s*)"(?:\\.|[^"\\])*"/giu, '$1"<redacted>"')
31
+ .replace(/((?:api[_-]?key|access[_-]?token|refresh[_-]?token|token|secret|password)\s*[=:]\s*)[^\s,;}]+/giu, '$1<redacted>')
32
+ .replace(/(https?:\/\/[^\s/:@]+:)[^\s/@]+@/giu, '$1<redacted>@');
33
+ return value.length <= maxChars ? value : `${value.slice(0, maxChars)}...[truncated]`;
34
+ }
35
+
36
+ function buildRuntimeExitRecord(input) {
37
+ const now = typeof input.now === 'function' ? input.now() : new Date();
38
+ const record = { schemaVersion: 1, ts: new Date(now).toISOString() };
39
+ for (const key of ['source', 'kind', 'signal', 'phase', 'cliVersion', 'profile', 'sessionId', 'lastTool', 'detail']) {
40
+ if (input[key] !== undefined && input[key] !== '') record[key] = sanitizeExitText(input[key]);
41
+ }
42
+ for (const [key, value] of Object.entries({ exitCode: input.exitCode, pid: input.pid ?? process.pid,
43
+ ppid: input.ppid ?? process.ppid, childPid: input.childPid })) {
44
+ if (Number.isInteger(value)) record[key] = value;
45
+ }
46
+ if (typeof input.loaded === 'boolean') record.loaded = input.loaded;
47
+ if (input.error !== undefined && input.error !== null) {
48
+ record.error = {
49
+ name: sanitizeExitText(input.error.name || 'Error', 160),
50
+ message: sanitizeExitText(input.error.message || input.error),
51
+ stack: sanitizeExitText(input.error.stack || '', 8192),
52
+ };
53
+ }
54
+ return record;
55
+ }
56
+
57
+ function directoryIdentity(directory) {
58
+ const stat = fs.lstatSync(directory, { bigint: true });
59
+ if (!stat.isDirectory() || stat.isSymbolicLink()) throw new Error('Unsafe exit directory');
60
+ return stat;
61
+ }
62
+
63
+ function sameIdentity(left, right) {
64
+ return left.dev === right.dev && left.ino === right.ino;
65
+ }
66
+
67
+ function regularLedger(stat) {
68
+ if (!stat.isFile() || stat.isSymbolicLink() || stat.nlink !== 1n) throw new Error('Unsafe exit ledger');
69
+ return stat;
70
+ }
71
+
72
+ function trimLedger(descriptor, maxBytes, retainBytes, incomingBytes) {
73
+ const size = Number(regularLedger(fs.fstatSync(descriptor, { bigint: true })).size);
74
+ if (!Number.isSafeInteger(size)) throw new Error('Invalid exit ledger size');
75
+ if (size + incomingBytes <= maxBytes) return;
76
+ const keepBytes = Math.max(0, Math.min(size, retainBytes, maxBytes - incomingBytes));
77
+ const contents = Buffer.alloc(keepBytes);
78
+ const read = fs.readSync(descriptor, contents, 0, keepBytes, size - keepBytes);
79
+ const tail = contents.subarray(0, read);
80
+ const lineStart = size > keepBytes ? tail.indexOf(0x0a) + 1 : 0;
81
+ fs.ftruncateSync(descriptor, 0);
82
+ fs.writeFileSync(descriptor, lineStart > 0 || size === keepBytes ? tail.subarray(lineStart) : '');
83
+ }
84
+
85
+ function recordRuntimeExit(input = {}) {
86
+ let descriptor;
87
+ let appendDescriptor;
88
+ try {
89
+ const homeDir = typeof input.homeDir === 'string' ? input.homeDir.trim() : '';
90
+ if (!homeDir || !path.isAbsolute(homeDir)) return false;
91
+ // Pin the private home and diagnostic directory before opening any output.
92
+ fs.mkdirSync(homeDir, { recursive: true, mode: 0o700 });
93
+ const homeIdentity = directoryIdentity(homeDir);
94
+ const file = runtimeExitLedgerPath(homeDir);
95
+ const directory = path.dirname(file);
96
+ try { fs.mkdirSync(directory, { mode: 0o700 }); }
97
+ catch (error) { if (error.code !== 'EEXIST') throw error; }
98
+ const directoryStat = directoryIdentity(directory);
99
+ let before;
100
+ try { before = regularLedger(fs.lstatSync(file, { bigint: true })); }
101
+ catch (error) { if (error.code !== 'ENOENT') throw error; }
102
+ const flags = fs.constants.O_RDWR | (fs.constants.O_NOFOLLOW ?? 0)
103
+ | (fs.constants.O_NONBLOCK ?? 0) | (before ? 0 : fs.constants.O_CREAT | fs.constants.O_EXCL);
104
+ descriptor = fs.openSync(file, flags, 0o600);
105
+ const opened = regularLedger(fs.fstatSync(descriptor, { bigint: true }));
106
+ if ((before && !sameIdentity(before, opened))
107
+ || !sameIdentity(opened, regularLedger(fs.lstatSync(file, { bigint: true })))
108
+ || !sameIdentity(homeIdentity, directoryIdentity(homeDir))
109
+ || !sameIdentity(directoryStat, directoryIdentity(directory))) throw new Error('Exit ledger changed');
110
+ const maxBytes = Math.max(1024, Number.isInteger(input.maxBytes) ? input.maxBytes : DEFAULT_MAX_BYTES);
111
+ const retainBytes = Math.max(0, Number.isInteger(input.retainBytes) ? input.retainBytes : DEFAULT_RETAIN_BYTES);
112
+ const record = buildRuntimeExitRecord(input);
113
+ let line = `${JSON.stringify(record)}\n`;
114
+ // Retain identity/exit fields even when a diagnostic detail exceeds the cap.
115
+ for (const key of ['error', 'detail', 'lastTool', 'sessionId', 'profile', 'cliVersion', 'phase', 'source', 'kind', 'signal']) {
116
+ if (Buffer.byteLength(line) <= maxBytes) break;
117
+ delete record[key];
118
+ record.truncated = true;
119
+ line = `${JSON.stringify(record)}\n`;
120
+ }
121
+ trimLedger(descriptor, maxBytes, retainBytes, Buffer.byteLength(line));
122
+ // Windows append-only handles cannot truncate. Keep both handles bound to
123
+ // the same checked inode, using the second handle only for atomic append.
124
+ appendDescriptor = fs.openSync(file, fs.constants.O_WRONLY | fs.constants.O_APPEND
125
+ | (fs.constants.O_NOFOLLOW ?? 0) | (fs.constants.O_NONBLOCK ?? 0));
126
+ if (!sameIdentity(opened, regularLedger(fs.fstatSync(appendDescriptor, { bigint: true })))
127
+ || !sameIdentity(opened, regularLedger(fs.lstatSync(file, { bigint: true })))
128
+ || !sameIdentity(homeIdentity, directoryIdentity(homeDir))
129
+ || !sameIdentity(directoryStat, directoryIdentity(directory))) throw new Error('Exit ledger changed');
130
+ fs.appendFileSync(appendDescriptor, line, { encoding: 'utf8' });
131
+ try { fs.fchmodSync(descriptor, 0o600); } catch {}
132
+ return true;
133
+ } catch {
134
+ return false;
135
+ } finally {
136
+ if (appendDescriptor !== undefined) { try { fs.closeSync(appendDescriptor); } catch {} }
137
+ if (descriptor !== undefined) { try { fs.closeSync(descriptor); } catch {} }
138
+ }
139
+ }
140
+
141
+ module.exports = {
142
+ DEFAULT_MAX_BYTES, DEFAULT_RETAIN_BYTES, buildRuntimeExitRecord, recordRuntimeExit,
143
+ runtimeExitLedgerPath, sanitizeExitText,
144
+ };
@@ -1,23 +1,23 @@
1
- export interface RuntimeExitInput {
2
- homeDir?: string;
3
- source?: string;
4
- kind?: string;
5
- signal?: string;
6
- phase?: string;
7
- cliVersion?: string;
8
- profile?: string;
9
- sessionId?: string;
10
- lastTool?: string;
11
- detail?: string;
12
- exitCode?: number;
13
- pid?: number;
14
- ppid?: number;
15
- childPid?: number;
16
- loaded?: boolean;
17
- error?: unknown;
18
- maxBytes?: number;
19
- retainBytes?: number;
20
- now?: () => Date | number | string;
21
- }
22
-
23
- export function recordRuntimeExit(input?: RuntimeExitInput): boolean;
1
+ export interface RuntimeExitInput {
2
+ homeDir?: string;
3
+ source?: string;
4
+ kind?: string;
5
+ signal?: string;
6
+ phase?: string;
7
+ cliVersion?: string;
8
+ profile?: string;
9
+ sessionId?: string;
10
+ lastTool?: string;
11
+ detail?: string;
12
+ exitCode?: number;
13
+ pid?: number;
14
+ ppid?: number;
15
+ childPid?: number;
16
+ loaded?: boolean;
17
+ error?: unknown;
18
+ maxBytes?: number;
19
+ retainBytes?: number;
20
+ now?: () => Date | number | string;
21
+ }
22
+
23
+ export function recordRuntimeExit(input?: RuntimeExitInput): boolean;