blun-king-cli 9.1.602 → 9.1.604
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +118 -99
- package/LIESMICH.txt +74 -57
- package/README.md +119 -76
- package/bin/agentspine-king-goal-inbox.mjs +127 -127
- package/bin/agentspine-king-goal-intake.mjs +106 -106
- package/bin/agentspine-king-host-runner.mjs +109 -109
- package/bin/agentspine-king-snapshot-policy.cjs +80 -80
- package/bin/agentspine-king-status-policy.mjs +226 -226
- package/bin/agentspine-king-worker-host.mjs +160 -160
- package/bin/core-bootstrap.js +77 -77
- package/bin/launcher-mode.js +93 -93
- package/bin/launcher-restart-policy.cjs +131 -131
- package/bin/launcher-runtime.js +491 -491
- package/bin/managed-node.js +186 -186
- package/bin/managed-plugin-selection.cjs +115 -115
- package/bin/native-module-repair.js +201 -201
- package/bin/node-runtime.js +34 -34
- package/bin/node-version.js +49 -49
- package/bin/plugin-bootstrap.js +162 -162
- package/bin/private-paths.js +1606 -1606
- package/bin/runtime-exit-ledger.cjs +144 -144
- package/bin/runtime-exit-ledger.d.cts +23 -23
- package/bin/standard-tools-bootstrap.js +272 -272
- package/bin/turn-thinking-policy.cjs +82 -82
- package/bin/update-copy.js +200 -200
- package/bin/update-lease.js +597 -597
- package/bin/update-notice.js +1568 -1568
- package/bin/verify-agent-behavior.cjs +122 -122
- package/bin/verify-agent-components.cjs +104 -104
- package/bin/verify-bundled-agent-sources.cjs +98 -76
- package/bin/windows-node-crash-dump.cjs +289 -289
- package/blun.mjs +8041 -6407
- package/codebase-index/codebase_index.py +470 -470
- package/dist-web/assets/{CodeBlockNode-CXnTLMNr.js → CodeBlockNode-TKKkJX8L.js} +1 -1
- 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
- package/dist-web/assets/DesignSystemView-DwK1aQMo.js +12 -0
- package/dist-web/assets/DesignSystemView-szjTj7ns.css +1 -0
- package/dist-web/assets/{Tooltip-CFQEzqC-.js → Tooltip-CTWv7WCU.js} +1 -1
- package/dist-web/assets/{abnfDiagram-N423BO3Z-Z1APZG5m.js → abnfDiagram-N423BO3Z-DA8V8Zjh.js} +1 -1
- package/dist-web/assets/{arc-C4VyWloT.js → arc-YtddKbdY.js} +1 -1
- package/dist-web/assets/{architectureDiagram-T3A2C74G-DpddbUcM.js → architectureDiagram-T3A2C74G-CGEiObmo.js} +1 -1
- package/dist-web/assets/{blockDiagram-VBNYF7ZC-BqccOSMr.js → blockDiagram-VBNYF7ZC-Bd5Vt50e.js} +1 -1
- package/dist-web/assets/{c4Diagram-5PPSVZJV-OHAcJrTy.js → c4Diagram-5PPSVZJV-D2ApPM54.js} +1 -1
- package/dist-web/assets/channel-BnluexuN.js +1 -0
- package/dist-web/assets/{chunk-2GRJ4B5K-DqJHs8Fw.js → chunk-2GRJ4B5K-BDcIEZSl.js} +1 -1
- package/dist-web/assets/{chunk-2Q5K7J3B-B1nCd91o.js → chunk-2Q5K7J3B-6aSabCX7.js} +1 -1
- package/dist-web/assets/{chunk-5RXB4S5H-neG70kZx.js → chunk-5RXB4S5H-BFRQvIJS.js} +1 -1
- package/dist-web/assets/{chunk-5VM5RSS4-l7biGqeH.js → chunk-5VM5RSS4-ChAa-DNY.js} +1 -1
- package/dist-web/assets/{chunk-6Q2QTUOP-BO-F11Gs.js → chunk-6Q2QTUOP-BIfrb2Jc.js} +1 -1
- package/dist-web/assets/{chunk-GF5L2VYU-BtC69fw0.js → chunk-GF5L2VYU-BR0gvtWI.js} +1 -1
- package/dist-web/assets/{chunk-JWPE2WC7-DIOubEqr.js → chunk-JWPE2WC7-BgXKsx5E.js} +1 -1
- package/dist-web/assets/{chunk-KBJHAD2P-DDUeH_Ba.js → chunk-KBJHAD2P-I8mrZhpz.js} +1 -1
- package/dist-web/assets/{chunk-RYQCIY6F-B9gFMVbS.js → chunk-RYQCIY6F-BMPaem92.js} +1 -1
- package/dist-web/assets/{chunk-XXDRQBXY-Bn1Xz6TQ.js → chunk-XXDRQBXY-COy0J-0j.js} +1 -1
- package/dist-web/assets/classDiagram-JCYQIIEL-BliFdSD9.js +1 -0
- package/dist-web/assets/classDiagram-v2-OCEON4UE-BliFdSD9.js +1 -0
- package/dist-web/assets/{cose-bilkent-JH36ORCC-DqigO3T4.js → cose-bilkent-JH36ORCC-MosTTRdo.js} +1 -1
- package/dist-web/assets/{cynefin-VYW2F7L2-rpD93tQ-.js → cynefin-VYW2F7L2-5tjPgrkP.js} +1 -1
- package/dist-web/assets/{cynefinDiagram-MW4NZA55-DbTqnkVJ.js → cynefinDiagram-MW4NZA55-CbDcdInR.js} +1 -1
- package/dist-web/assets/{dagre-VZM6K2ZE-DcM8aEVY.js → dagre-VZM6K2ZE-YaBom1ZH.js} +1 -1
- package/dist-web/assets/{diagram-7IWD3JNH-UymgsBNa.js → diagram-7IWD3JNH-Da4m-Hgz.js} +1 -1
- package/dist-web/assets/{diagram-B4RE2ZJO-BiYu5WPF.js → diagram-B4RE2ZJO-CAUUegxg.js} +1 -1
- package/dist-web/assets/{diagram-LBJQPF4R-BD3kkgOO.js → diagram-LBJQPF4R-o21mWKG4.js} +1 -1
- package/dist-web/assets/{diagram-Q27KOJAE-D1mYQ_cI.js → diagram-Q27KOJAE-BA3z3sXf.js} +1 -1
- package/dist-web/assets/{diagram-UB23O5K3-BtX8u6HG.js → diagram-UB23O5K3-CGRdSSDK.js} +1 -1
- package/dist-web/assets/{ebnfDiagram-BXEA7PRR-CBAnFkRF.js → ebnfDiagram-BXEA7PRR-Bw-iU9FP.js} +1 -1
- package/dist-web/assets/{erDiagram-JOGREHBK-3vKcWsxm.js → erDiagram-JOGREHBK-BwM1yiR0.js} +1 -1
- package/dist-web/assets/{flowDiagram-UKHOOZJN-D9wBbHm2.js → flowDiagram-UKHOOZJN-z05eOR1Q.js} +1 -1
- package/dist-web/assets/{ganttDiagram-PKOTCBZU-Cd_dViF-.js → ganttDiagram-PKOTCBZU-BMUubNTl.js} +1 -1
- package/dist-web/assets/{gitGraphDiagram-DS77QQ5N-BiEEtTo7.js → gitGraphDiagram-DS77QQ5N-Dccbi1wU.js} +1 -1
- package/dist-web/assets/{index-DdEakFJM.js → index-BBbC5LRF.js} +1 -1
- package/dist-web/assets/{index-BmKj7CB1.css → index-C9eDH3u-.css} +1 -1
- package/dist-web/assets/{index-Ba1OvLjf.js → index-D48p0M2S.js} +2 -2
- package/dist-web/assets/{index-CiPA8Kex.js → index-h0KEOmkU.js} +14 -14
- package/dist-web/assets/{index10-CNr4U-Nw.js → index10-Qbuw1N6u.js} +1 -1
- package/dist-web/assets/{index11-Dto_yZNz.js → index11-g8v025wN.js} +1 -1
- package/dist-web/assets/{index5-B2PldaO2.js → index5-BYNcV8BY.js} +1 -1
- package/dist-web/assets/{index6-CBdZmmyx.js → index6-BetANtnV.js} +1 -1
- package/dist-web/assets/{index7-Dz9oCsUF.js → index7-BcPbgCKN.js} +1 -1
- package/dist-web/assets/{index8-B6k25jtU.js → index8-B1lp-RvF.js} +2 -2
- package/dist-web/assets/{infoDiagram-6WML65LV-D4ph2sIH.js → infoDiagram-6WML65LV-BF_pbUnN.js} +1 -1
- package/dist-web/assets/{ishikawaDiagram-WSZJBQD7-DjKjbMm9.js → ishikawaDiagram-WSZJBQD7-Dedv4urS.js} +1 -1
- package/dist-web/assets/{journeyDiagram-NVQOT4AX-DttHDfPQ.js → journeyDiagram-NVQOT4AX-B1Ex3b6o.js} +1 -1
- package/dist-web/assets/{kanban-definition-27J2QSJJ-C5gB-W0d.js → kanban-definition-27J2QSJJ-BtCGliBH.js} +1 -1
- package/dist-web/assets/{linear-BRGe4-d6.js → linear-ByItc8Qr.js} +1 -1
- package/dist-web/assets/{mermaid.core-D-9Sx1No.js → mermaid.core-B1M2O7nN.js} +4 -4
- package/dist-web/assets/{mindmap-definition-FAOFIHXS-Cg_hoVIq.js → mindmap-definition-FAOFIHXS-D0UHKUyj.js} +1 -1
- package/dist-web/assets/{pegDiagram-VL7TDLO6-CSX9hitD.js → pegDiagram-VL7TDLO6-D7zYCVmG.js} +1 -1
- package/dist-web/assets/{pieDiagram-7S7Q4E2Y-0L0mxahZ.js → pieDiagram-7S7Q4E2Y-Bd0i9CHa.js} +1 -1
- package/dist-web/assets/{quadrantDiagram-CIZ2JOQS-U483z0er.js → quadrantDiagram-CIZ2JOQS-MG-Ug47d.js} +1 -1
- package/dist-web/assets/{railroadDiagram-AXF67PYL-D80a2_DJ.js → railroadDiagram-AXF67PYL-AZdl90Gj.js} +1 -1
- package/dist-web/assets/{requirementDiagram-LRYGKXZP-CeyHfq8t.js → requirementDiagram-LRYGKXZP-DjplF9kt.js} +1 -1
- package/dist-web/assets/{sankeyDiagram-W5VNT64P-CCISkkrR.js → sankeyDiagram-W5VNT64P-Bi9Zgfjs.js} +1 -1
- package/dist-web/assets/{sequenceDiagram-SI44F4Z6-jQfm2XS7.js → sequenceDiagram-SI44F4Z6-ChEpdCvb.js} +1 -1
- package/dist-web/assets/{sizeCapture-X5ZJPWSS-lRZsmCoS.js → sizeCapture-X5ZJPWSS-B5RFUdaa.js} +1 -1
- package/dist-web/assets/{stateDiagram-OKZ733FA-ClCgTdUm.js → stateDiagram-OKZ733FA-BkRblSKp.js} +1 -1
- package/dist-web/assets/stateDiagram-v2-UEYNNEHI-fVkQcACW.js +1 -0
- package/dist-web/assets/{swimlanes-SLNWSIFB-BCdOUN05.js → swimlanes-SLNWSIFB-DLXH6Zhc.js} +2 -2
- package/dist-web/assets/swimlanesDiagram-ULZ7WXOC-Bg-BiZJQ.js +8 -0
- package/dist-web/assets/{timeline-definition-Z64GVDOM-DpWutSKa.js → timeline-definition-Z64GVDOM-C_N88siO.js} +1 -1
- package/dist-web/assets/{vennDiagram-T6HMQDX7-DVC5JVsc.js → vennDiagram-T6HMQDX7-DMsnvDt0.js} +1 -1
- package/dist-web/assets/{vue.runtime.esm-bundler-BwwRtQwQ.js → vue.runtime.esm-bundler-B-3uIy85.js} +1 -1
- package/dist-web/assets/{wardleyDiagram-T6FBY63Y-BK2mrq7L.js → wardleyDiagram-T6FBY63Y-C9SJIPzq.js} +1 -1
- package/dist-web/assets/{xychartDiagram-ELKLHX3M-DUJu9F7-.js → xychartDiagram-ELKLHX3M-DZLWA6Go.js} +1 -1
- package/dist-web/index.html +32 -32
- package/dist-web/vis/index.html.gz +0 -0
- package/package.json +1 -1
- package/scripts/fix-node-pty-perms.js +53 -53
- package/standard-skills/blun-app-design-system/SKILL.md +30 -30
- package/standard-skills/blun-app-design-system/provenance.json +17 -17
- package/standard-skills/blun-app-design-system/references/tokens.md +48 -48
- package/standard-skills/blun-app-design-system/scripts/check-blun-design.mjs +49 -49
- package/telegram-plugin/blun.plugin.json +20 -20
- package/telegram-plugin/commands/access.md +37 -37
- package/telegram-plugin/dist/bridge.mjs +117 -100
- package/telegram-plugin/dist/mcp-server.mjs +185 -161
- package/worker-host.mjs +1458 -1022
- package/dist-web/assets/DesignSystemView-6HgAUEmC.css +0 -1
- package/dist-web/assets/DesignSystemView-BbMOu-XO.js +0 -12
- package/dist-web/assets/channel-B2VOQOI-.js +0 -1
- package/dist-web/assets/classDiagram-JCYQIIEL-BA1TjAxj.js +0 -1
- package/dist-web/assets/classDiagram-v2-OCEON4UE-BA1TjAxj.js +0 -1
- package/dist-web/assets/stateDiagram-v2-UEYNNEHI-fi_lrTAx.js +0 -1
- package/dist-web/assets/swimlanesDiagram-ULZ7WXOC-BUd_uYK3.js +0 -8
package/bin/launcher-mode.js
CHANGED
|
@@ -1,93 +1,93 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const fs = require('node:fs');
|
|
4
|
-
const path = require('node:path');
|
|
5
|
-
|
|
6
|
-
const LAUNCHER_MODES = Object.freeze({
|
|
7
|
-
BLUN: 'blun',
|
|
8
|
-
KING: 'king',
|
|
9
|
-
});
|
|
10
|
-
|
|
11
|
-
const LAUNCHER_HELP_TEXT = [
|
|
12
|
-
'',
|
|
13
|
-
'Launchers:',
|
|
14
|
-
' blun Starts the CLI without automatically attaching Telegram.',
|
|
15
|
-
' king Starts the same CLI and automatically attaches the configured Telegram channel.',
|
|
16
|
-
' Both use the same version, account, model, and commands.',
|
|
17
|
-
' blun tools list shows optional tools; enable writes only explicit choices.',
|
|
18
|
-
'',
|
|
19
|
-
].join('\n');
|
|
20
|
-
|
|
21
|
-
function launcherModeFromArgv(argv) {
|
|
22
|
-
const entry = argv[1] || '';
|
|
23
|
-
const filename = entry.replaceAll('\\', '/').split('/').pop() || '';
|
|
24
|
-
const command = filename.replace(/\.(?:c|m)?js$/i, '').toLowerCase();
|
|
25
|
-
return command === LAUNCHER_MODES.KING ? LAUNCHER_MODES.KING : LAUNCHER_MODES.BLUN;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
function runtimePolicyEnvironment(blunDir) {
|
|
29
|
-
if (blunDir === undefined) return {};
|
|
30
|
-
let policy;
|
|
31
|
-
try {
|
|
32
|
-
if (!path.isAbsolute(blunDir)) throw new Error('Invalid home');
|
|
33
|
-
const file = path.join(blunDir, 'runtime-policy.json');
|
|
34
|
-
const stat = fs.lstatSync(file);
|
|
35
|
-
if (!stat.isFile() || stat.isSymbolicLink() || stat.size > 4096) throw new Error('Invalid file');
|
|
36
|
-
policy = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
37
|
-
} catch (error) {
|
|
38
|
-
if (error.code === 'ENOENT') return {};
|
|
39
|
-
throw new Error('Invalid BLUN runtime policy');
|
|
40
|
-
}
|
|
41
|
-
const review = policy?.responseReview;
|
|
42
|
-
if (policy?.schema !== 'blun.runtime-policy/v1'
|
|
43
|
-
|| Object.keys(policy).sort().join(',') !== 'responseReview,schema'
|
|
44
|
-
|| !review || Object.keys(review).sort().join(',') !== 'guardVersion,language,required'
|
|
45
|
-
|| review.required !== true || typeof review.language !== 'string'
|
|
46
|
-
|| !/^[A-Za-z]{2,3}(?:-[A-Za-z0-9]{2,8})*$/.test(review.language)
|
|
47
|
-
|| /^(auto|all)$/i.test(review.language)
|
|
48
|
-
|| typeof review.guardVersion !== 'string' || !/^\d+\.\d+\.\d+$/.test(review.guardVersion)) {
|
|
49
|
-
throw new Error('Invalid BLUN runtime policy');
|
|
50
|
-
}
|
|
51
|
-
return {
|
|
52
|
-
BLUN_LANGUAGE_GUARD_MANDATORY: '1',
|
|
53
|
-
BLUN_LANGUAGE_GUARD_LANGUAGE: review.language,
|
|
54
|
-
BLUN_LANGUAGE_GUARD_VERSION: review.guardVersion,
|
|
55
|
-
BLUN_TELEGRAM_LANGUAGE_GATE: 'required',
|
|
56
|
-
BLUN_TELEGRAM_OUTPUT_LOCALE: review.language,
|
|
57
|
-
AGENTSPINE_STATE_DIR: path.join(blunDir, 'state', 'agent-spine'),
|
|
58
|
-
};
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
function createLauncherEnvironment(sourceEnv, mode, publicPackageVersion, blunDir) {
|
|
62
|
-
return {
|
|
63
|
-
...sourceEnv,
|
|
64
|
-
...runtimePolicyEnvironment(blunDir),
|
|
65
|
-
BLUN_NO_AUTO_UPDATE: '1',
|
|
66
|
-
BLUN_PUBLIC_PACKAGE_VERSION: publicPackageVersion,
|
|
67
|
-
BLUN_TELEGRAM_ATTACH: mode === LAUNCHER_MODES.KING ? 'on' : 'off',
|
|
68
|
-
BLUN_TELEGRAM_HEADLESS: 'off',
|
|
69
|
-
};
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
function isNpmPackageUpdateRequest(args) {
|
|
73
|
-
if (!Array.isArray(args) || args.length !== 1) return false;
|
|
74
|
-
const command = String(args[0] || '').toLowerCase();
|
|
75
|
-
return command === 'update' || command === 'upgrade';
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
function isStandaloneHelpRequest(args) {
|
|
79
|
-
return Array.isArray(args) && args.length === 1 && (args[0] === '--help' || args[0] === '-h');
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
function launcherHelpText() {
|
|
83
|
-
return LAUNCHER_HELP_TEXT;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
module.exports = {
|
|
87
|
-
LAUNCHER_MODES,
|
|
88
|
-
createLauncherEnvironment,
|
|
89
|
-
isNpmPackageUpdateRequest,
|
|
90
|
-
isStandaloneHelpRequest,
|
|
91
|
-
launcherHelpText,
|
|
92
|
-
launcherModeFromArgv,
|
|
93
|
-
};
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('node:fs');
|
|
4
|
+
const path = require('node:path');
|
|
5
|
+
|
|
6
|
+
const LAUNCHER_MODES = Object.freeze({
|
|
7
|
+
BLUN: 'blun',
|
|
8
|
+
KING: 'king',
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
const LAUNCHER_HELP_TEXT = [
|
|
12
|
+
'',
|
|
13
|
+
'Launchers:',
|
|
14
|
+
' blun Starts the CLI without automatically attaching Telegram.',
|
|
15
|
+
' king Starts the same CLI and automatically attaches the configured Telegram channel.',
|
|
16
|
+
' Both use the same version, account, model, and commands.',
|
|
17
|
+
' blun tools list shows optional tools; enable writes only explicit choices.',
|
|
18
|
+
'',
|
|
19
|
+
].join('\n');
|
|
20
|
+
|
|
21
|
+
function launcherModeFromArgv(argv) {
|
|
22
|
+
const entry = argv[1] || '';
|
|
23
|
+
const filename = entry.replaceAll('\\', '/').split('/').pop() || '';
|
|
24
|
+
const command = filename.replace(/\.(?:c|m)?js$/i, '').toLowerCase();
|
|
25
|
+
return command === LAUNCHER_MODES.KING ? LAUNCHER_MODES.KING : LAUNCHER_MODES.BLUN;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function runtimePolicyEnvironment(blunDir) {
|
|
29
|
+
if (blunDir === undefined) return {};
|
|
30
|
+
let policy;
|
|
31
|
+
try {
|
|
32
|
+
if (!path.isAbsolute(blunDir)) throw new Error('Invalid home');
|
|
33
|
+
const file = path.join(blunDir, 'runtime-policy.json');
|
|
34
|
+
const stat = fs.lstatSync(file);
|
|
35
|
+
if (!stat.isFile() || stat.isSymbolicLink() || stat.size > 4096) throw new Error('Invalid file');
|
|
36
|
+
policy = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
37
|
+
} catch (error) {
|
|
38
|
+
if (error.code === 'ENOENT') return {};
|
|
39
|
+
throw new Error('Invalid BLUN runtime policy');
|
|
40
|
+
}
|
|
41
|
+
const review = policy?.responseReview;
|
|
42
|
+
if (policy?.schema !== 'blun.runtime-policy/v1'
|
|
43
|
+
|| Object.keys(policy).sort().join(',') !== 'responseReview,schema'
|
|
44
|
+
|| !review || Object.keys(review).sort().join(',') !== 'guardVersion,language,required'
|
|
45
|
+
|| review.required !== true || typeof review.language !== 'string'
|
|
46
|
+
|| !/^[A-Za-z]{2,3}(?:-[A-Za-z0-9]{2,8})*$/.test(review.language)
|
|
47
|
+
|| /^(auto|all)$/i.test(review.language)
|
|
48
|
+
|| typeof review.guardVersion !== 'string' || !/^\d+\.\d+\.\d+$/.test(review.guardVersion)) {
|
|
49
|
+
throw new Error('Invalid BLUN runtime policy');
|
|
50
|
+
}
|
|
51
|
+
return {
|
|
52
|
+
BLUN_LANGUAGE_GUARD_MANDATORY: '1',
|
|
53
|
+
BLUN_LANGUAGE_GUARD_LANGUAGE: review.language,
|
|
54
|
+
BLUN_LANGUAGE_GUARD_VERSION: review.guardVersion,
|
|
55
|
+
BLUN_TELEGRAM_LANGUAGE_GATE: 'required',
|
|
56
|
+
BLUN_TELEGRAM_OUTPUT_LOCALE: review.language,
|
|
57
|
+
AGENTSPINE_STATE_DIR: path.join(blunDir, 'state', 'agent-spine'),
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function createLauncherEnvironment(sourceEnv, mode, publicPackageVersion, blunDir) {
|
|
62
|
+
return {
|
|
63
|
+
...sourceEnv,
|
|
64
|
+
...runtimePolicyEnvironment(blunDir),
|
|
65
|
+
BLUN_NO_AUTO_UPDATE: '1',
|
|
66
|
+
BLUN_PUBLIC_PACKAGE_VERSION: publicPackageVersion,
|
|
67
|
+
BLUN_TELEGRAM_ATTACH: mode === LAUNCHER_MODES.KING ? 'on' : 'off',
|
|
68
|
+
BLUN_TELEGRAM_HEADLESS: 'off',
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function isNpmPackageUpdateRequest(args) {
|
|
73
|
+
if (!Array.isArray(args) || args.length !== 1) return false;
|
|
74
|
+
const command = String(args[0] || '').toLowerCase();
|
|
75
|
+
return command === 'update' || command === 'upgrade';
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function isStandaloneHelpRequest(args) {
|
|
79
|
+
return Array.isArray(args) && args.length === 1 && (args[0] === '--help' || args[0] === '-h');
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function launcherHelpText() {
|
|
83
|
+
return LAUNCHER_HELP_TEXT;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
module.exports = {
|
|
87
|
+
LAUNCHER_MODES,
|
|
88
|
+
createLauncherEnvironment,
|
|
89
|
+
isNpmPackageUpdateRequest,
|
|
90
|
+
isStandaloneHelpRequest,
|
|
91
|
+
launcherHelpText,
|
|
92
|
+
launcherModeFromArgv,
|
|
93
|
+
};
|
|
@@ -1,131 +1,131 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const crypto = require('node:crypto');
|
|
4
|
-
const fs = require('node:fs');
|
|
5
|
-
const path = require('node:path');
|
|
6
|
-
|
|
7
|
-
const RESTART_WINDOW_MS = 60 * 60 * 1000;
|
|
8
|
-
const MAX_RESTARTS_PER_WINDOW = 3;
|
|
9
|
-
const RESTART_DELAYS_MS = Object.freeze([1000, 3000, 5000]);
|
|
10
|
-
const INTENTIONAL_STOP_TTL_MS = 2 * 60 * 1000;
|
|
11
|
-
const OPERATOR_SIGNALS = new Set(['SIGHUP', 'SIGINT', 'SIGTERM']);
|
|
12
|
-
|
|
13
|
-
function restartBudgetPath(homeDir) {
|
|
14
|
-
return path.join(path.resolve(homeDir), 'diagnostics', 'launcher-restart-budget.json');
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
function intentionalStopMarkerPath(homeDir) {
|
|
18
|
-
return path.join(path.resolve(homeDir), 'diagnostics', 'intentional-core-stop.json');
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
function launcherStatusQueuePath(homeDir, stateDir) {
|
|
22
|
-
return path.join(stateDir ? path.resolve(stateDir) : path.join(path.resolve(homeDir), 'channels', 'telegram'),
|
|
23
|
-
'launcher-status-queue.jsonl');
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
function atomicWriteJson(file, value, fsImpl = fs) {
|
|
27
|
-
fsImpl.mkdirSync(path.dirname(file), { recursive: true, mode: 0o700 });
|
|
28
|
-
const temporary = `${file}.${process.pid}.${crypto.randomUUID()}.tmp`;
|
|
29
|
-
try {
|
|
30
|
-
fsImpl.writeFileSync(temporary, `${JSON.stringify(value)}\n`, { encoding: 'utf8', mode: 0o600, flag: 'wx' });
|
|
31
|
-
fsImpl.renameSync(temporary, file);
|
|
32
|
-
} finally {
|
|
33
|
-
try { fsImpl.rmSync(temporary, { force: true }); } catch {}
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function claimLauncherRestart(options) {
|
|
38
|
-
const nowMs = Number.isFinite(options.nowMs) ? options.nowMs : Date.now();
|
|
39
|
-
const fsImpl = options.fsImpl || fs;
|
|
40
|
-
const file = restartBudgetPath(options.homeDir);
|
|
41
|
-
const denied = { allowed: false, attempt: MAX_RESTARTS_PER_WINDOW, limit: MAX_RESTARTS_PER_WINDOW, windowMs: RESTART_WINDOW_MS };
|
|
42
|
-
let attempts = [];
|
|
43
|
-
try {
|
|
44
|
-
const stat = fsImpl.lstatSync(file);
|
|
45
|
-
if (!stat.isFile() || stat.isSymbolicLink() || stat.size > 4096) return denied;
|
|
46
|
-
const saved = JSON.parse(fsImpl.readFileSync(file, 'utf8'));
|
|
47
|
-
if (saved?.schema !== 'blun.launcher-restart-budget/v1' || !Array.isArray(saved.attempts)
|
|
48
|
-
|| saved.attempts.some((value) => !Number.isFinite(value) || value > nowMs)) return denied;
|
|
49
|
-
attempts = saved.attempts.filter((value) => value > nowMs - RESTART_WINDOW_MS);
|
|
50
|
-
} catch (error) {
|
|
51
|
-
if (error.code !== 'ENOENT') return denied;
|
|
52
|
-
}
|
|
53
|
-
if (attempts.length >= MAX_RESTARTS_PER_WINDOW) return { ...denied, attempt: attempts.length };
|
|
54
|
-
attempts.push(nowMs);
|
|
55
|
-
try {
|
|
56
|
-
atomicWriteJson(file, {
|
|
57
|
-
schema: 'blun.launcher-restart-budget/v1', updatedAt: new Date(nowMs).toISOString(), attempts,
|
|
58
|
-
}, fsImpl);
|
|
59
|
-
} catch {
|
|
60
|
-
// Never restart without a durable budget claim.
|
|
61
|
-
return denied;
|
|
62
|
-
}
|
|
63
|
-
return { allowed: true, attempt: attempts.length, limit: MAX_RESTARTS_PER_WINDOW, windowMs: RESTART_WINDOW_MS };
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
function isWindowsFailFastExitCode(code) {
|
|
67
|
-
if (!Number.isInteger(code)) return false;
|
|
68
|
-
const unsigned = code >>> 0;
|
|
69
|
-
return unsigned >= 0xC0000000 && unsigned !== 0xC000013A;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
function isAbnormalCoreExit(result, loaded) {
|
|
73
|
-
if (OPERATOR_SIGNALS.has(result?.signal)) return false;
|
|
74
|
-
return Boolean(result?.error) || (loaded === true && isWindowsFailFastExitCode(result?.code));
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
function writeIntentionalStopMarker(options) {
|
|
78
|
-
const nowMs = Number.isFinite(options.nowMs) ? options.nowMs : Date.now();
|
|
79
|
-
const file = intentionalStopMarkerPath(options.homeDir);
|
|
80
|
-
atomicWriteJson(file, {
|
|
81
|
-
schema: 'blun.intentional-core-stop/v1', pid: options.pid,
|
|
82
|
-
createdAtMs: nowMs, expiresAtMs: nowMs + INTENTIONAL_STOP_TTL_MS,
|
|
83
|
-
}, options.fsImpl);
|
|
84
|
-
return file;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
function consumeIntentionalStopMarker(options) {
|
|
88
|
-
const nowMs = Number.isFinite(options.nowMs) ? options.nowMs : Date.now();
|
|
89
|
-
const file = intentionalStopMarkerPath(options.homeDir);
|
|
90
|
-
const fsImpl = options.fsImpl || fs;
|
|
91
|
-
let marker;
|
|
92
|
-
try {
|
|
93
|
-
const stat = fsImpl.lstatSync(file);
|
|
94
|
-
if (!stat.isFile() || stat.isSymbolicLink() || stat.size > 4096) return false;
|
|
95
|
-
marker = JSON.parse(fsImpl.readFileSync(file, 'utf8'));
|
|
96
|
-
fsImpl.rmSync(file);
|
|
97
|
-
} catch { return false; }
|
|
98
|
-
return marker?.schema === 'blun.intentional-core-stop/v1'
|
|
99
|
-
&& Number.isInteger(marker.pid) && marker.pid === options.childPid
|
|
100
|
-
&& Number.isFinite(marker.createdAtMs) && Number.isFinite(marker.expiresAtMs)
|
|
101
|
-
&& marker.createdAtMs <= nowMs && marker.expiresAtMs >= nowMs
|
|
102
|
-
&& marker.expiresAtMs - marker.createdAtMs <= INTENTIONAL_STOP_TTL_MS;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
function restartDelayMs(attempt) {
|
|
106
|
-
return RESTART_DELAYS_MS[Math.max(0, Math.min(RESTART_DELAYS_MS.length - 1, attempt - 1))];
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
function appendLauncherStatus(options) {
|
|
110
|
-
const nowMs = Number.isFinite(options.nowMs) ? options.nowMs : Date.now();
|
|
111
|
-
const exhausted = options.kind === 'launcher-restart-exhausted';
|
|
112
|
-
const signal = options.signal ? ` signal=${options.signal}` : '';
|
|
113
|
-
const event = {
|
|
114
|
-
schema: 'blun.launcher-status/v1', id: crypto.randomUUID(), createdAt: new Date(nowMs).toISOString(),
|
|
115
|
-
kind: options.kind, attempt: options.attempt, limit: options.limit,
|
|
116
|
-
exitCode: options.exitCode, signal: options.signal,
|
|
117
|
-
text: `[launcher.restart] ${exhausted ? 'stopped ' : ''}${options.attempt}/${options.limit} exit=${options.exitCode}${signal}`,
|
|
118
|
-
};
|
|
119
|
-
const file = launcherStatusQueuePath(options.homeDir, options.stateDir);
|
|
120
|
-
const fsImpl = options.fsImpl || fs;
|
|
121
|
-
fsImpl.mkdirSync(path.dirname(file), { recursive: true, mode: 0o700 });
|
|
122
|
-
fsImpl.appendFileSync(file, `${JSON.stringify(event)}\n`, { encoding: 'utf8', mode: 0o600 });
|
|
123
|
-
return event;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
module.exports = {
|
|
127
|
-
INTENTIONAL_STOP_TTL_MS, MAX_RESTARTS_PER_WINDOW, RESTART_DELAYS_MS, RESTART_WINDOW_MS,
|
|
128
|
-
appendLauncherStatus, claimLauncherRestart, consumeIntentionalStopMarker, intentionalStopMarkerPath,
|
|
129
|
-
isAbnormalCoreExit, isWindowsFailFastExitCode, launcherStatusQueuePath, restartBudgetPath,
|
|
130
|
-
restartDelayMs, writeIntentionalStopMarker,
|
|
131
|
-
};
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const crypto = require('node:crypto');
|
|
4
|
+
const fs = require('node:fs');
|
|
5
|
+
const path = require('node:path');
|
|
6
|
+
|
|
7
|
+
const RESTART_WINDOW_MS = 60 * 60 * 1000;
|
|
8
|
+
const MAX_RESTARTS_PER_WINDOW = 3;
|
|
9
|
+
const RESTART_DELAYS_MS = Object.freeze([1000, 3000, 5000]);
|
|
10
|
+
const INTENTIONAL_STOP_TTL_MS = 2 * 60 * 1000;
|
|
11
|
+
const OPERATOR_SIGNALS = new Set(['SIGHUP', 'SIGINT', 'SIGTERM']);
|
|
12
|
+
|
|
13
|
+
function restartBudgetPath(homeDir) {
|
|
14
|
+
return path.join(path.resolve(homeDir), 'diagnostics', 'launcher-restart-budget.json');
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function intentionalStopMarkerPath(homeDir) {
|
|
18
|
+
return path.join(path.resolve(homeDir), 'diagnostics', 'intentional-core-stop.json');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function launcherStatusQueuePath(homeDir, stateDir) {
|
|
22
|
+
return path.join(stateDir ? path.resolve(stateDir) : path.join(path.resolve(homeDir), 'channels', 'telegram'),
|
|
23
|
+
'launcher-status-queue.jsonl');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function atomicWriteJson(file, value, fsImpl = fs) {
|
|
27
|
+
fsImpl.mkdirSync(path.dirname(file), { recursive: true, mode: 0o700 });
|
|
28
|
+
const temporary = `${file}.${process.pid}.${crypto.randomUUID()}.tmp`;
|
|
29
|
+
try {
|
|
30
|
+
fsImpl.writeFileSync(temporary, `${JSON.stringify(value)}\n`, { encoding: 'utf8', mode: 0o600, flag: 'wx' });
|
|
31
|
+
fsImpl.renameSync(temporary, file);
|
|
32
|
+
} finally {
|
|
33
|
+
try { fsImpl.rmSync(temporary, { force: true }); } catch {}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function claimLauncherRestart(options) {
|
|
38
|
+
const nowMs = Number.isFinite(options.nowMs) ? options.nowMs : Date.now();
|
|
39
|
+
const fsImpl = options.fsImpl || fs;
|
|
40
|
+
const file = restartBudgetPath(options.homeDir);
|
|
41
|
+
const denied = { allowed: false, attempt: MAX_RESTARTS_PER_WINDOW, limit: MAX_RESTARTS_PER_WINDOW, windowMs: RESTART_WINDOW_MS };
|
|
42
|
+
let attempts = [];
|
|
43
|
+
try {
|
|
44
|
+
const stat = fsImpl.lstatSync(file);
|
|
45
|
+
if (!stat.isFile() || stat.isSymbolicLink() || stat.size > 4096) return denied;
|
|
46
|
+
const saved = JSON.parse(fsImpl.readFileSync(file, 'utf8'));
|
|
47
|
+
if (saved?.schema !== 'blun.launcher-restart-budget/v1' || !Array.isArray(saved.attempts)
|
|
48
|
+
|| saved.attempts.some((value) => !Number.isFinite(value) || value > nowMs)) return denied;
|
|
49
|
+
attempts = saved.attempts.filter((value) => value > nowMs - RESTART_WINDOW_MS);
|
|
50
|
+
} catch (error) {
|
|
51
|
+
if (error.code !== 'ENOENT') return denied;
|
|
52
|
+
}
|
|
53
|
+
if (attempts.length >= MAX_RESTARTS_PER_WINDOW) return { ...denied, attempt: attempts.length };
|
|
54
|
+
attempts.push(nowMs);
|
|
55
|
+
try {
|
|
56
|
+
atomicWriteJson(file, {
|
|
57
|
+
schema: 'blun.launcher-restart-budget/v1', updatedAt: new Date(nowMs).toISOString(), attempts,
|
|
58
|
+
}, fsImpl);
|
|
59
|
+
} catch {
|
|
60
|
+
// Never restart without a durable budget claim.
|
|
61
|
+
return denied;
|
|
62
|
+
}
|
|
63
|
+
return { allowed: true, attempt: attempts.length, limit: MAX_RESTARTS_PER_WINDOW, windowMs: RESTART_WINDOW_MS };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function isWindowsFailFastExitCode(code) {
|
|
67
|
+
if (!Number.isInteger(code)) return false;
|
|
68
|
+
const unsigned = code >>> 0;
|
|
69
|
+
return unsigned >= 0xC0000000 && unsigned !== 0xC000013A;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function isAbnormalCoreExit(result, loaded) {
|
|
73
|
+
if (OPERATOR_SIGNALS.has(result?.signal)) return false;
|
|
74
|
+
return Boolean(result?.error) || (loaded === true && isWindowsFailFastExitCode(result?.code));
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function writeIntentionalStopMarker(options) {
|
|
78
|
+
const nowMs = Number.isFinite(options.nowMs) ? options.nowMs : Date.now();
|
|
79
|
+
const file = intentionalStopMarkerPath(options.homeDir);
|
|
80
|
+
atomicWriteJson(file, {
|
|
81
|
+
schema: 'blun.intentional-core-stop/v1', pid: options.pid,
|
|
82
|
+
createdAtMs: nowMs, expiresAtMs: nowMs + INTENTIONAL_STOP_TTL_MS,
|
|
83
|
+
}, options.fsImpl);
|
|
84
|
+
return file;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function consumeIntentionalStopMarker(options) {
|
|
88
|
+
const nowMs = Number.isFinite(options.nowMs) ? options.nowMs : Date.now();
|
|
89
|
+
const file = intentionalStopMarkerPath(options.homeDir);
|
|
90
|
+
const fsImpl = options.fsImpl || fs;
|
|
91
|
+
let marker;
|
|
92
|
+
try {
|
|
93
|
+
const stat = fsImpl.lstatSync(file);
|
|
94
|
+
if (!stat.isFile() || stat.isSymbolicLink() || stat.size > 4096) return false;
|
|
95
|
+
marker = JSON.parse(fsImpl.readFileSync(file, 'utf8'));
|
|
96
|
+
fsImpl.rmSync(file);
|
|
97
|
+
} catch { return false; }
|
|
98
|
+
return marker?.schema === 'blun.intentional-core-stop/v1'
|
|
99
|
+
&& Number.isInteger(marker.pid) && marker.pid === options.childPid
|
|
100
|
+
&& Number.isFinite(marker.createdAtMs) && Number.isFinite(marker.expiresAtMs)
|
|
101
|
+
&& marker.createdAtMs <= nowMs && marker.expiresAtMs >= nowMs
|
|
102
|
+
&& marker.expiresAtMs - marker.createdAtMs <= INTENTIONAL_STOP_TTL_MS;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function restartDelayMs(attempt) {
|
|
106
|
+
return RESTART_DELAYS_MS[Math.max(0, Math.min(RESTART_DELAYS_MS.length - 1, attempt - 1))];
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function appendLauncherStatus(options) {
|
|
110
|
+
const nowMs = Number.isFinite(options.nowMs) ? options.nowMs : Date.now();
|
|
111
|
+
const exhausted = options.kind === 'launcher-restart-exhausted';
|
|
112
|
+
const signal = options.signal ? ` signal=${options.signal}` : '';
|
|
113
|
+
const event = {
|
|
114
|
+
schema: 'blun.launcher-status/v1', id: crypto.randomUUID(), createdAt: new Date(nowMs).toISOString(),
|
|
115
|
+
kind: options.kind, attempt: options.attempt, limit: options.limit,
|
|
116
|
+
exitCode: options.exitCode, signal: options.signal,
|
|
117
|
+
text: `[launcher.restart] ${exhausted ? 'stopped ' : ''}${options.attempt}/${options.limit} exit=${options.exitCode}${signal}`,
|
|
118
|
+
};
|
|
119
|
+
const file = launcherStatusQueuePath(options.homeDir, options.stateDir);
|
|
120
|
+
const fsImpl = options.fsImpl || fs;
|
|
121
|
+
fsImpl.mkdirSync(path.dirname(file), { recursive: true, mode: 0o700 });
|
|
122
|
+
fsImpl.appendFileSync(file, `${JSON.stringify(event)}\n`, { encoding: 'utf8', mode: 0o600 });
|
|
123
|
+
return event;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
module.exports = {
|
|
127
|
+
INTENTIONAL_STOP_TTL_MS, MAX_RESTARTS_PER_WINDOW, RESTART_DELAYS_MS, RESTART_WINDOW_MS,
|
|
128
|
+
appendLauncherStatus, claimLauncherRestart, consumeIntentionalStopMarker, intentionalStopMarkerPath,
|
|
129
|
+
isAbnormalCoreExit, isWindowsFailFastExitCode, launcherStatusQueuePath, restartBudgetPath,
|
|
130
|
+
restartDelayMs, writeIntentionalStopMarker,
|
|
131
|
+
};
|