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,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;