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.
- package/CHANGELOG.md +105 -94
- package/LIESMICH.txt +57 -57
- package/README.md +90 -90
- 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 +83 -83
- package/bin/windows-node-crash-dump.cjs +289 -289
- package/blun.mjs +1709 -1601
- 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 +81 -81
- package/telegram-plugin/dist/mcp-server.mjs +158 -158
- package/worker-host.mjs +932 -932
- 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
|
@@ -1,122 +1,122 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
const fs = require('node:fs');
|
|
3
|
-
const path = require('node:path');
|
|
4
|
-
const os = require('node:os');
|
|
5
|
-
const vm = require('node:vm');
|
|
6
|
-
const assert = require('node:assert/strict');
|
|
7
|
-
|
|
8
|
-
function functionSource(source, name) {
|
|
9
|
-
const found = source.match(new RegExp('^function ' + name + '\\([\\s\\S]*?^\\}', 'm'))?.[0];
|
|
10
|
-
if (!found) throw new Error('Missing executable contract: ' + name);
|
|
11
|
-
return found;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
function verifyAgentBehavior(packageRoot, reportParent) {
|
|
15
|
-
const parent = reportParent || path.join(os.homedir(), '.blun', 'diagnostics', 'package-contracts');
|
|
16
|
-
fs.mkdirSync(parent, { recursive: true });
|
|
17
|
-
const work = fs.mkdtempSync(path.join(parent, 'check-'));
|
|
18
|
-
const passed = [];
|
|
19
|
-
const notApplicable = [];
|
|
20
|
-
const write = (file, data) => {
|
|
21
|
-
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
22
|
-
fs.writeFileSync(file, typeof data === 'string' ? data : JSON.stringify(data));
|
|
23
|
-
};
|
|
24
|
-
const read = file => JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
25
|
-
const core = fs.readFileSync(path.join(packageRoot, 'blun.mjs'), 'utf8');
|
|
26
|
-
const plugin = require(path.join(packageRoot, 'bin/plugin-bootstrap.js'));
|
|
27
|
-
const selected = require(path.join(packageRoot, 'bin/managed-plugin-selection.cjs'));
|
|
28
|
-
const tools = require(path.join(packageRoot, 'bin/standard-tools-bootstrap.js'));
|
|
29
|
-
const profile = path.join(work, 'profile');
|
|
30
|
-
plugin.installManagedAgentSpinePlugin({ packageRoot, blunDir: profile });
|
|
31
|
-
plugin.installManagedTelegramPlugin({ packageRoot, blunDir: profile });
|
|
32
|
-
tools.seedStandardTools({ packageRoot, blunDir: profile, names: ['blun-language-guard'], env: {} });
|
|
33
|
-
const registryFile = path.join(profile, 'plugins/installed.json');
|
|
34
|
-
const registry = read(registryFile);
|
|
35
|
-
const spine = registry.plugins.find(item => item.id === 'agent-spine');
|
|
36
|
-
const telegram = registry.plugins.find(item => item.id === 'telegram');
|
|
37
|
-
assert(spine?.enabled && telegram?.enabled, 'native startup omitted a bundled plugin');
|
|
38
|
-
assert(fs.existsSync(path.join(spine.root, 'src/hook.js')));
|
|
39
|
-
const mcpFile = path.join(profile, 'mcp.json');
|
|
40
|
-
const mcp = read(mcpFile);
|
|
41
|
-
const guard = mcp.mcpServers['blun-language-guard'];
|
|
42
|
-
assert.equal(guard.enabled, true);
|
|
43
|
-
assert.equal(guard.args[0], path.join(packageRoot, 'standard-tools/language-guard/blun_language_guard.py'));
|
|
44
|
-
passed.push('bootstrap-components-register-spine-telegram-guard');
|
|
45
|
-
|
|
46
|
-
spine.managedSelection = selected.describeAgentSpineSelection({ blunDir: profile, pluginRoot: spine.root });
|
|
47
|
-
write(registryFile, registry);
|
|
48
|
-
const before = fs.readFileSync(registryFile);
|
|
49
|
-
plugin.installManagedAgentSpinePlugin({ packageRoot, blunDir: profile });
|
|
50
|
-
assert.deepEqual(fs.readFileSync(registryFile), before);
|
|
51
|
-
guard.enabled = false; write(mcpFile, mcp);
|
|
52
|
-
tools.seedStandardTools({ packageRoot, blunDir: profile, names: ['blun-language-guard'], env: {} });
|
|
53
|
-
assert.equal(read(mcpFile).mcpServers['blun-language-guard'].enabled, false);
|
|
54
|
-
guard.args = [path.join(work, 'custom/blun_language_guard.py'), 'serve'];
|
|
55
|
-
guard.env = { SYNTHETIC_CUSTOM_SETTING: 'retained' }; write(mcpFile, mcp);
|
|
56
|
-
const custom = fs.readFileSync(mcpFile);
|
|
57
|
-
tools.seedStandardTools({ packageRoot, blunDir: profile, names: ['blun-language-guard'], env: {} });
|
|
58
|
-
assert.deepEqual(fs.readFileSync(mcpFile), custom);
|
|
59
|
-
passed.push('repeat-start-preserves-selected-plugin-disabled-and-custom-guard');
|
|
60
|
-
|
|
61
|
-
const sharedHome = path.join(work, 'old-user-home');
|
|
62
|
-
const staleRoot = path.join(sharedHome, '.blun/plugins/managed/old-telegram');
|
|
63
|
-
write(path.join(staleRoot, '.mcp.json'), { mcpServers: {} });
|
|
64
|
-
write(path.join(staleRoot, 'dist/mcp-server.mjs'), '// Synthetic old installed entrypoint.\n');
|
|
65
|
-
write(path.join(sharedHome, '.blun/plugins/installed.json'), { plugins: [{ id: 'telegram', enabled: true, root: staleRoot }] });
|
|
66
|
-
const env = { BLUN_HOME: profile };
|
|
67
|
-
const resolveRoot = vm.runInNewContext(functionSource(core, 'managedTelegramPluginRoot') + '\nmanagedTelegramPluginRoot;', {
|
|
68
|
-
process: { env }, join: path.join, homedir: () => sharedHome,
|
|
69
|
-
existsSync: fs.existsSync, readFileSync: fs.readFileSync,
|
|
70
|
-
});
|
|
71
|
-
assert.equal(resolveRoot(), telegram.root, 'bridge selected the old shared registration');
|
|
72
|
-
telegram.enabled = false; write(registryFile, registry);
|
|
73
|
-
assert.equal(resolveRoot(), undefined, 'disabled plugin was selected');
|
|
74
|
-
telegram.enabled = true; write(registryFile, registry);
|
|
75
|
-
passed.push('packed-host-selects-active-profile-not-stale-shared-copy');
|
|
76
|
-
|
|
77
|
-
const bridge = fs.readFileSync(path.join(telegram.root, 'dist/bridge.mjs'), 'utf8');
|
|
78
|
-
const access = { dmPolicy: 'allowlist', allowFrom: ['operator'], pending: {},
|
|
79
|
-
groups: { '-1001': { requireMention: true, alwaysAllowFrom: ['operator'], allowFrom: [] } } };
|
|
80
|
-
const gateHelpers = ['matchesAny', 'isMentioned', 'gate',
|
|
81
|
-
...(/^function connectionCode\(/m.test(bridge) ? ['connectionCode']
|
|
82
|
-
: ['isGermanGroupBroadcast', 'botLoopGuardAllows'])];
|
|
83
|
-
const gate = vm.runInNewContext(gateHelpers
|
|
84
|
-
.map(name => functionSource(bridge, name)).join('\n') + '\ngate;', {
|
|
85
|
-
readAccess: () => access, pruneExpired: () => false,
|
|
86
|
-
saveAccess: () => { throw new Error('unexpected access change'); },
|
|
87
|
-
BOT_COOLDOWN_MS: 15000, lastBotDelivery: new Map(),
|
|
88
|
-
});
|
|
89
|
-
const message = { chatId: '-1001', chatType: 'supergroup', text: 'Status for another participant', from: { id: 'operator' } };
|
|
90
|
-
assert.equal(gate(message, 'ExampleBot').action, 'drop');
|
|
91
|
-
assert.equal(gate({ ...message, from: { id: 'another-bot', isBot: true } }, 'ExampleBot').action, 'drop');
|
|
92
|
-
assert.equal(gate({ ...message, text: '@ExampleBot inspect', entities: [{ type: 'mention', offset: 0, length: 11 }] }, 'ExampleBot').action, 'deliver');
|
|
93
|
-
assert.equal(gate({ ...message, replyToUsername: 'ExampleBot' }, 'ExampleBot').action, 'deliver');
|
|
94
|
-
assert.equal(gate({ ...message, chatType: 'private', chatId: 'operator' }, 'ExampleBot').action, 'deliver');
|
|
95
|
-
passed.push('registered-bridge-drops-unmentioned-groups-preserves-mentions-replies-dms');
|
|
96
|
-
|
|
97
|
-
const thinkingFile = path.join(packageRoot, 'bin/thinking-only-guard.cjs');
|
|
98
|
-
if (fs.existsSync(thinkingFile)) {
|
|
99
|
-
const thinking = require(thinkingFile);
|
|
100
|
-
assert.equal(thinking.DEFAULT_THINKING_ONLY_LIMIT_MS, 120000);
|
|
101
|
-
const original = { messages: [{ role: 'user', content: 'synthetic inspection' }] };
|
|
102
|
-
assert.equal(thinking.forceAnswerParams(original).thinkingOnlyRecovery, true);
|
|
103
|
-
assert(core.includes('thinkingOnlyRecovery') && core.includes('withThinking("off")'), 'thinking recovery integration missing');
|
|
104
|
-
passed.push('bounded-thinking-recovery-retained-structural-only');
|
|
105
|
-
} else {
|
|
106
|
-
assert(fs.existsSync(path.join(packageRoot, 'bundled-agent-sources.json')), 'private thinking helper missing');
|
|
107
|
-
notApplicable.push({ check: 'private-thinking-helper', reason: 'The regular CLI does not ship this private test API; no recovery or host proof.' });
|
|
108
|
-
}
|
|
109
|
-
const routing = require(path.join(packageRoot, 'bin/turn-thinking-policy.cjs'));
|
|
110
|
-
for (const prompt of ['Which result file belongs to our task?', 'Quick status?', 'Do you remember our previous decision?']) {
|
|
111
|
-
assert.equal(routing.selectThinkingEffortForTurn(prompt, 'user', 'off'), undefined,
|
|
112
|
-
'continuity request lost its project context');
|
|
113
|
-
}
|
|
114
|
-
assert.equal(routing.selectThinkingEffortForTurn('Hi', 'user'), 'low');
|
|
115
|
-
passed.push('continuity-requests-retain-normal-context-routing');
|
|
116
|
-
const report = { verified: true, work, passed, notApplicable, launcherAutomaticRegistrationTested: false, modelApplicationTested: false,
|
|
117
|
-
hubCompletenessTested: false, realProfilesChanged: false };
|
|
118
|
-
write(path.join(work, 'report.json'), report);
|
|
119
|
-
return report;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
module.exports = { verifyAgentBehavior, functionSource };
|
|
1
|
+
'use strict';
|
|
2
|
+
const fs = require('node:fs');
|
|
3
|
+
const path = require('node:path');
|
|
4
|
+
const os = require('node:os');
|
|
5
|
+
const vm = require('node:vm');
|
|
6
|
+
const assert = require('node:assert/strict');
|
|
7
|
+
|
|
8
|
+
function functionSource(source, name) {
|
|
9
|
+
const found = source.match(new RegExp('^function ' + name + '\\([\\s\\S]*?^\\}', 'm'))?.[0];
|
|
10
|
+
if (!found) throw new Error('Missing executable contract: ' + name);
|
|
11
|
+
return found;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function verifyAgentBehavior(packageRoot, reportParent) {
|
|
15
|
+
const parent = reportParent || path.join(os.homedir(), '.blun', 'diagnostics', 'package-contracts');
|
|
16
|
+
fs.mkdirSync(parent, { recursive: true });
|
|
17
|
+
const work = fs.mkdtempSync(path.join(parent, 'check-'));
|
|
18
|
+
const passed = [];
|
|
19
|
+
const notApplicable = [];
|
|
20
|
+
const write = (file, data) => {
|
|
21
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
22
|
+
fs.writeFileSync(file, typeof data === 'string' ? data : JSON.stringify(data));
|
|
23
|
+
};
|
|
24
|
+
const read = file => JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
25
|
+
const core = fs.readFileSync(path.join(packageRoot, 'blun.mjs'), 'utf8');
|
|
26
|
+
const plugin = require(path.join(packageRoot, 'bin/plugin-bootstrap.js'));
|
|
27
|
+
const selected = require(path.join(packageRoot, 'bin/managed-plugin-selection.cjs'));
|
|
28
|
+
const tools = require(path.join(packageRoot, 'bin/standard-tools-bootstrap.js'));
|
|
29
|
+
const profile = path.join(work, 'profile');
|
|
30
|
+
plugin.installManagedAgentSpinePlugin({ packageRoot, blunDir: profile });
|
|
31
|
+
plugin.installManagedTelegramPlugin({ packageRoot, blunDir: profile });
|
|
32
|
+
tools.seedStandardTools({ packageRoot, blunDir: profile, names: ['blun-language-guard'], env: {} });
|
|
33
|
+
const registryFile = path.join(profile, 'plugins/installed.json');
|
|
34
|
+
const registry = read(registryFile);
|
|
35
|
+
const spine = registry.plugins.find(item => item.id === 'agent-spine');
|
|
36
|
+
const telegram = registry.plugins.find(item => item.id === 'telegram');
|
|
37
|
+
assert(spine?.enabled && telegram?.enabled, 'native startup omitted a bundled plugin');
|
|
38
|
+
assert(fs.existsSync(path.join(spine.root, 'src/hook.js')));
|
|
39
|
+
const mcpFile = path.join(profile, 'mcp.json');
|
|
40
|
+
const mcp = read(mcpFile);
|
|
41
|
+
const guard = mcp.mcpServers['blun-language-guard'];
|
|
42
|
+
assert.equal(guard.enabled, true);
|
|
43
|
+
assert.equal(guard.args[0], path.join(packageRoot, 'standard-tools/language-guard/blun_language_guard.py'));
|
|
44
|
+
passed.push('bootstrap-components-register-spine-telegram-guard');
|
|
45
|
+
|
|
46
|
+
spine.managedSelection = selected.describeAgentSpineSelection({ blunDir: profile, pluginRoot: spine.root });
|
|
47
|
+
write(registryFile, registry);
|
|
48
|
+
const before = fs.readFileSync(registryFile);
|
|
49
|
+
plugin.installManagedAgentSpinePlugin({ packageRoot, blunDir: profile });
|
|
50
|
+
assert.deepEqual(fs.readFileSync(registryFile), before);
|
|
51
|
+
guard.enabled = false; write(mcpFile, mcp);
|
|
52
|
+
tools.seedStandardTools({ packageRoot, blunDir: profile, names: ['blun-language-guard'], env: {} });
|
|
53
|
+
assert.equal(read(mcpFile).mcpServers['blun-language-guard'].enabled, false);
|
|
54
|
+
guard.args = [path.join(work, 'custom/blun_language_guard.py'), 'serve'];
|
|
55
|
+
guard.env = { SYNTHETIC_CUSTOM_SETTING: 'retained' }; write(mcpFile, mcp);
|
|
56
|
+
const custom = fs.readFileSync(mcpFile);
|
|
57
|
+
tools.seedStandardTools({ packageRoot, blunDir: profile, names: ['blun-language-guard'], env: {} });
|
|
58
|
+
assert.deepEqual(fs.readFileSync(mcpFile), custom);
|
|
59
|
+
passed.push('repeat-start-preserves-selected-plugin-disabled-and-custom-guard');
|
|
60
|
+
|
|
61
|
+
const sharedHome = path.join(work, 'old-user-home');
|
|
62
|
+
const staleRoot = path.join(sharedHome, '.blun/plugins/managed/old-telegram');
|
|
63
|
+
write(path.join(staleRoot, '.mcp.json'), { mcpServers: {} });
|
|
64
|
+
write(path.join(staleRoot, 'dist/mcp-server.mjs'), '// Synthetic old installed entrypoint.\n');
|
|
65
|
+
write(path.join(sharedHome, '.blun/plugins/installed.json'), { plugins: [{ id: 'telegram', enabled: true, root: staleRoot }] });
|
|
66
|
+
const env = { BLUN_HOME: profile };
|
|
67
|
+
const resolveRoot = vm.runInNewContext(functionSource(core, 'managedTelegramPluginRoot') + '\nmanagedTelegramPluginRoot;', {
|
|
68
|
+
process: { env }, join: path.join, homedir: () => sharedHome,
|
|
69
|
+
existsSync: fs.existsSync, readFileSync: fs.readFileSync,
|
|
70
|
+
});
|
|
71
|
+
assert.equal(resolveRoot(), telegram.root, 'bridge selected the old shared registration');
|
|
72
|
+
telegram.enabled = false; write(registryFile, registry);
|
|
73
|
+
assert.equal(resolveRoot(), undefined, 'disabled plugin was selected');
|
|
74
|
+
telegram.enabled = true; write(registryFile, registry);
|
|
75
|
+
passed.push('packed-host-selects-active-profile-not-stale-shared-copy');
|
|
76
|
+
|
|
77
|
+
const bridge = fs.readFileSync(path.join(telegram.root, 'dist/bridge.mjs'), 'utf8');
|
|
78
|
+
const access = { dmPolicy: 'allowlist', allowFrom: ['operator'], pending: {},
|
|
79
|
+
groups: { '-1001': { requireMention: true, alwaysAllowFrom: ['operator'], allowFrom: [] } } };
|
|
80
|
+
const gateHelpers = ['matchesAny', 'isMentioned', 'gate',
|
|
81
|
+
...(/^function connectionCode\(/m.test(bridge) ? ['connectionCode']
|
|
82
|
+
: ['isGermanGroupBroadcast', 'botLoopGuardAllows'])];
|
|
83
|
+
const gate = vm.runInNewContext(gateHelpers
|
|
84
|
+
.map(name => functionSource(bridge, name)).join('\n') + '\ngate;', {
|
|
85
|
+
readAccess: () => access, pruneExpired: () => false,
|
|
86
|
+
saveAccess: () => { throw new Error('unexpected access change'); },
|
|
87
|
+
BOT_COOLDOWN_MS: 15000, lastBotDelivery: new Map(),
|
|
88
|
+
});
|
|
89
|
+
const message = { chatId: '-1001', chatType: 'supergroup', text: 'Status for another participant', from: { id: 'operator' } };
|
|
90
|
+
assert.equal(gate(message, 'ExampleBot').action, 'drop');
|
|
91
|
+
assert.equal(gate({ ...message, from: { id: 'another-bot', isBot: true } }, 'ExampleBot').action, 'drop');
|
|
92
|
+
assert.equal(gate({ ...message, text: '@ExampleBot inspect', entities: [{ type: 'mention', offset: 0, length: 11 }] }, 'ExampleBot').action, 'deliver');
|
|
93
|
+
assert.equal(gate({ ...message, replyToUsername: 'ExampleBot' }, 'ExampleBot').action, 'deliver');
|
|
94
|
+
assert.equal(gate({ ...message, chatType: 'private', chatId: 'operator' }, 'ExampleBot').action, 'deliver');
|
|
95
|
+
passed.push('registered-bridge-drops-unmentioned-groups-preserves-mentions-replies-dms');
|
|
96
|
+
|
|
97
|
+
const thinkingFile = path.join(packageRoot, 'bin/thinking-only-guard.cjs');
|
|
98
|
+
if (fs.existsSync(thinkingFile)) {
|
|
99
|
+
const thinking = require(thinkingFile);
|
|
100
|
+
assert.equal(thinking.DEFAULT_THINKING_ONLY_LIMIT_MS, 120000);
|
|
101
|
+
const original = { messages: [{ role: 'user', content: 'synthetic inspection' }] };
|
|
102
|
+
assert.equal(thinking.forceAnswerParams(original).thinkingOnlyRecovery, true);
|
|
103
|
+
assert(core.includes('thinkingOnlyRecovery') && core.includes('withThinking("off")'), 'thinking recovery integration missing');
|
|
104
|
+
passed.push('bounded-thinking-recovery-retained-structural-only');
|
|
105
|
+
} else {
|
|
106
|
+
assert(fs.existsSync(path.join(packageRoot, 'bundled-agent-sources.json')), 'private thinking helper missing');
|
|
107
|
+
notApplicable.push({ check: 'private-thinking-helper', reason: 'The regular CLI does not ship this private test API; no recovery or host proof.' });
|
|
108
|
+
}
|
|
109
|
+
const routing = require(path.join(packageRoot, 'bin/turn-thinking-policy.cjs'));
|
|
110
|
+
for (const prompt of ['Which result file belongs to our task?', 'Quick status?', 'Do you remember our previous decision?']) {
|
|
111
|
+
assert.equal(routing.selectThinkingEffortForTurn(prompt, 'user', 'off'), undefined,
|
|
112
|
+
'continuity request lost its project context');
|
|
113
|
+
}
|
|
114
|
+
assert.equal(routing.selectThinkingEffortForTurn('Hi', 'user'), 'low');
|
|
115
|
+
passed.push('continuity-requests-retain-normal-context-routing');
|
|
116
|
+
const report = { verified: true, work, passed, notApplicable, launcherAutomaticRegistrationTested: false, modelApplicationTested: false,
|
|
117
|
+
hubCompletenessTested: false, realProfilesChanged: false };
|
|
118
|
+
write(path.join(work, 'report.json'), report);
|
|
119
|
+
return report;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
module.exports = { verifyAgentBehavior, functionSource };
|
|
@@ -1,104 +1,104 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const fs = require('node:fs');
|
|
4
|
-
const path = require('node:path');
|
|
5
|
-
const { createHash } = require('node:crypto');
|
|
6
|
-
|
|
7
|
-
const RECEIPT = 'agent-components.json';
|
|
8
|
-
const SCHEMA = 'blun.bundled-agent-components/v1';
|
|
9
|
-
const REQUIRED = [
|
|
10
|
-
'blun.mjs',
|
|
11
|
-
'telegram-plugin/dist/bridge.mjs',
|
|
12
|
-
'bin/thinking-only-guard.cjs',
|
|
13
|
-
'bin/turn-thinking-policy.cjs',
|
|
14
|
-
'bin/context-performance-policy.cjs',
|
|
15
|
-
'bin/verify-agent-behavior.cjs',
|
|
16
|
-
'agent-spine-plugin/blun.plugin.json',
|
|
17
|
-
'agent-spine-plugin/src/hook.js',
|
|
18
|
-
'agent-spine-plugin/src/mcp.js',
|
|
19
|
-
'agent-spine-plugin/skills/agent-spine/SKILL.md',
|
|
20
|
-
'standard-skills/translate-native/SKILL.md',
|
|
21
|
-
'standard-tools/language-guard/blun_language_guard.py',
|
|
22
|
-
'standard-tools/language-guard/check_diacritics.py',
|
|
23
|
-
'standard-tools/language-guard/guard_service_client.py',
|
|
24
|
-
'standard-tools/language-guard/language_quality.py',
|
|
25
|
-
'standard-tools/language-guard/translation_guard.py',
|
|
26
|
-
'bin/plugin-bootstrap.js',
|
|
27
|
-
'bin/managed-plugin-selection.cjs',
|
|
28
|
-
'bin/standard-tools-bootstrap.js',
|
|
29
|
-
'bin/launcher-runtime.js',
|
|
30
|
-
];
|
|
31
|
-
const PREFIXES = ['agent-spine-plugin/', 'standard-skills/translate-native/', 'standard-tools/language-guard/'];
|
|
32
|
-
const sha256 = bytes => createHash('sha256').update(bytes).digest('hex');
|
|
33
|
-
|
|
34
|
-
function inventory(root, prefix = '', excluded = []) {
|
|
35
|
-
const result = {};
|
|
36
|
-
for (const name of fs.readdirSync(root).sort()) {
|
|
37
|
-
const file = path.join(root, name);
|
|
38
|
-
const relative = prefix + name;
|
|
39
|
-
if (excluded.some(item => relative === item || relative.startsWith(item + '/'))) continue;
|
|
40
|
-
const stat = fs.lstatSync(file);
|
|
41
|
-
if (stat.isSymbolicLink()) throw new Error('Linked component path: ' + relative);
|
|
42
|
-
if (stat.isDirectory()) Object.assign(result, inventory(file, relative + '/', excluded));
|
|
43
|
-
else if (stat.isFile()) result[relative] = sha256(fs.readFileSync(file));
|
|
44
|
-
else throw new Error('Unsupported component path: ' + relative);
|
|
45
|
-
}
|
|
46
|
-
return result;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
function safeFile(root, relative) {
|
|
50
|
-
if (typeof relative !== 'string' || relative.includes('\\') || relative.includes(':')
|
|
51
|
-
|| relative.split('/').some(part => !part || part === '.' || part === '..')) {
|
|
52
|
-
throw new Error('Invalid component path');
|
|
53
|
-
}
|
|
54
|
-
let current = path.resolve(root);
|
|
55
|
-
for (const part of relative.split('/')) {
|
|
56
|
-
current = path.join(current, part);
|
|
57
|
-
if (fs.lstatSync(current).isSymbolicLink()) throw new Error('Linked component path: ' + relative);
|
|
58
|
-
}
|
|
59
|
-
return current;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
function verifyAgentComponents(packageRoot) {
|
|
63
|
-
const receipt = JSON.parse(fs.readFileSync(path.join(packageRoot, RECEIPT), 'utf8'));
|
|
64
|
-
if (receipt.schema !== SCHEMA || !receipt.files || typeof receipt.files !== 'object'
|
|
65
|
-
|| Array.isArray(receipt.files)) throw new Error('Invalid bundled component receipt');
|
|
66
|
-
for (const name of ['agentSpine', 'translateNative']) {
|
|
67
|
-
if (!/^[a-f0-9]{40}$/.test(receipt.sources?.[name]?.commit || '')) {
|
|
68
|
-
throw new Error('Missing bundled component source: ' + name);
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
for (const name of REQUIRED) {
|
|
72
|
-
if (!Object.hasOwn(receipt.files, name)) throw new Error('Missing bundled component: ' + name);
|
|
73
|
-
}
|
|
74
|
-
for (const [relative, expected] of Object.entries(receipt.files)) {
|
|
75
|
-
if (!/^[a-f0-9]{64}$/.test(expected) || sha256(fs.readFileSync(safeFile(packageRoot, relative))) !== expected) {
|
|
76
|
-
throw new Error('Bundled component digest mismatch: ' + relative);
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
for (const prefix of PREFIXES) {
|
|
80
|
-
const actual = Object.keys(inventory(safeFile(packageRoot, prefix.slice(0, -1)), prefix));
|
|
81
|
-
const expected = Object.keys(receipt.files).filter(file => file.startsWith(prefix)).sort();
|
|
82
|
-
if (JSON.stringify(actual.sort()) !== JSON.stringify(expected)) throw new Error('Unexpected bundled component files: ' + prefix);
|
|
83
|
-
}
|
|
84
|
-
const manifest = JSON.parse(fs.readFileSync(path.join(packageRoot, 'package.json'), 'utf8'));
|
|
85
|
-
if (manifest.scripts?.prepack !== 'node bin/verify-agent-components.cjs') {
|
|
86
|
-
throw new Error('Bundled component prepack check missing');
|
|
87
|
-
}
|
|
88
|
-
const launcher = fs.readFileSync(path.join(packageRoot, 'bin/launcher-runtime.js'), 'utf8');
|
|
89
|
-
if (!launcher.includes("require('./verify-agent-components.cjs').verifyAgentComponents(PKG);")) {
|
|
90
|
-
throw new Error('Bundled component startup check missing');
|
|
91
|
-
}
|
|
92
|
-
return { verified: true, files: Object.keys(receipt.files).length, sources: receipt.sources };
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
if (require.main === module) {
|
|
96
|
-
try {
|
|
97
|
-
const root = path.resolve(__dirname, '..');
|
|
98
|
-
console.log(JSON.stringify(verifyAgentComponents(root)));
|
|
99
|
-
console.log(JSON.stringify(require('./verify-agent-behavior.cjs').verifyAgentBehavior(root)));
|
|
100
|
-
}
|
|
101
|
-
catch (error) { console.error(error.message); process.exitCode = 1; }
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
module.exports = { inventory, sha256, verifyAgentComponents, SCHEMA, RECEIPT, REQUIRED, PREFIXES };
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('node:fs');
|
|
4
|
+
const path = require('node:path');
|
|
5
|
+
const { createHash } = require('node:crypto');
|
|
6
|
+
|
|
7
|
+
const RECEIPT = 'agent-components.json';
|
|
8
|
+
const SCHEMA = 'blun.bundled-agent-components/v1';
|
|
9
|
+
const REQUIRED = [
|
|
10
|
+
'blun.mjs',
|
|
11
|
+
'telegram-plugin/dist/bridge.mjs',
|
|
12
|
+
'bin/thinking-only-guard.cjs',
|
|
13
|
+
'bin/turn-thinking-policy.cjs',
|
|
14
|
+
'bin/context-performance-policy.cjs',
|
|
15
|
+
'bin/verify-agent-behavior.cjs',
|
|
16
|
+
'agent-spine-plugin/blun.plugin.json',
|
|
17
|
+
'agent-spine-plugin/src/hook.js',
|
|
18
|
+
'agent-spine-plugin/src/mcp.js',
|
|
19
|
+
'agent-spine-plugin/skills/agent-spine/SKILL.md',
|
|
20
|
+
'standard-skills/translate-native/SKILL.md',
|
|
21
|
+
'standard-tools/language-guard/blun_language_guard.py',
|
|
22
|
+
'standard-tools/language-guard/check_diacritics.py',
|
|
23
|
+
'standard-tools/language-guard/guard_service_client.py',
|
|
24
|
+
'standard-tools/language-guard/language_quality.py',
|
|
25
|
+
'standard-tools/language-guard/translation_guard.py',
|
|
26
|
+
'bin/plugin-bootstrap.js',
|
|
27
|
+
'bin/managed-plugin-selection.cjs',
|
|
28
|
+
'bin/standard-tools-bootstrap.js',
|
|
29
|
+
'bin/launcher-runtime.js',
|
|
30
|
+
];
|
|
31
|
+
const PREFIXES = ['agent-spine-plugin/', 'standard-skills/translate-native/', 'standard-tools/language-guard/'];
|
|
32
|
+
const sha256 = bytes => createHash('sha256').update(bytes).digest('hex');
|
|
33
|
+
|
|
34
|
+
function inventory(root, prefix = '', excluded = []) {
|
|
35
|
+
const result = {};
|
|
36
|
+
for (const name of fs.readdirSync(root).sort()) {
|
|
37
|
+
const file = path.join(root, name);
|
|
38
|
+
const relative = prefix + name;
|
|
39
|
+
if (excluded.some(item => relative === item || relative.startsWith(item + '/'))) continue;
|
|
40
|
+
const stat = fs.lstatSync(file);
|
|
41
|
+
if (stat.isSymbolicLink()) throw new Error('Linked component path: ' + relative);
|
|
42
|
+
if (stat.isDirectory()) Object.assign(result, inventory(file, relative + '/', excluded));
|
|
43
|
+
else if (stat.isFile()) result[relative] = sha256(fs.readFileSync(file));
|
|
44
|
+
else throw new Error('Unsupported component path: ' + relative);
|
|
45
|
+
}
|
|
46
|
+
return result;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function safeFile(root, relative) {
|
|
50
|
+
if (typeof relative !== 'string' || relative.includes('\\') || relative.includes(':')
|
|
51
|
+
|| relative.split('/').some(part => !part || part === '.' || part === '..')) {
|
|
52
|
+
throw new Error('Invalid component path');
|
|
53
|
+
}
|
|
54
|
+
let current = path.resolve(root);
|
|
55
|
+
for (const part of relative.split('/')) {
|
|
56
|
+
current = path.join(current, part);
|
|
57
|
+
if (fs.lstatSync(current).isSymbolicLink()) throw new Error('Linked component path: ' + relative);
|
|
58
|
+
}
|
|
59
|
+
return current;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function verifyAgentComponents(packageRoot) {
|
|
63
|
+
const receipt = JSON.parse(fs.readFileSync(path.join(packageRoot, RECEIPT), 'utf8'));
|
|
64
|
+
if (receipt.schema !== SCHEMA || !receipt.files || typeof receipt.files !== 'object'
|
|
65
|
+
|| Array.isArray(receipt.files)) throw new Error('Invalid bundled component receipt');
|
|
66
|
+
for (const name of ['agentSpine', 'translateNative']) {
|
|
67
|
+
if (!/^[a-f0-9]{40}$/.test(receipt.sources?.[name]?.commit || '')) {
|
|
68
|
+
throw new Error('Missing bundled component source: ' + name);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
for (const name of REQUIRED) {
|
|
72
|
+
if (!Object.hasOwn(receipt.files, name)) throw new Error('Missing bundled component: ' + name);
|
|
73
|
+
}
|
|
74
|
+
for (const [relative, expected] of Object.entries(receipt.files)) {
|
|
75
|
+
if (!/^[a-f0-9]{64}$/.test(expected) || sha256(fs.readFileSync(safeFile(packageRoot, relative))) !== expected) {
|
|
76
|
+
throw new Error('Bundled component digest mismatch: ' + relative);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
for (const prefix of PREFIXES) {
|
|
80
|
+
const actual = Object.keys(inventory(safeFile(packageRoot, prefix.slice(0, -1)), prefix));
|
|
81
|
+
const expected = Object.keys(receipt.files).filter(file => file.startsWith(prefix)).sort();
|
|
82
|
+
if (JSON.stringify(actual.sort()) !== JSON.stringify(expected)) throw new Error('Unexpected bundled component files: ' + prefix);
|
|
83
|
+
}
|
|
84
|
+
const manifest = JSON.parse(fs.readFileSync(path.join(packageRoot, 'package.json'), 'utf8'));
|
|
85
|
+
if (manifest.scripts?.prepack !== 'node bin/verify-agent-components.cjs') {
|
|
86
|
+
throw new Error('Bundled component prepack check missing');
|
|
87
|
+
}
|
|
88
|
+
const launcher = fs.readFileSync(path.join(packageRoot, 'bin/launcher-runtime.js'), 'utf8');
|
|
89
|
+
if (!launcher.includes("require('./verify-agent-components.cjs').verifyAgentComponents(PKG);")) {
|
|
90
|
+
throw new Error('Bundled component startup check missing');
|
|
91
|
+
}
|
|
92
|
+
return { verified: true, files: Object.keys(receipt.files).length, sources: receipt.sources };
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (require.main === module) {
|
|
96
|
+
try {
|
|
97
|
+
const root = path.resolve(__dirname, '..');
|
|
98
|
+
console.log(JSON.stringify(verifyAgentComponents(root)));
|
|
99
|
+
console.log(JSON.stringify(require('./verify-agent-behavior.cjs').verifyAgentBehavior(root)));
|
|
100
|
+
}
|
|
101
|
+
catch (error) { console.error(error.message); process.exitCode = 1; }
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
module.exports = { inventory, sha256, verifyAgentComponents, SCHEMA, RECEIPT, REQUIRED, PREFIXES };
|
|
@@ -1,83 +1,83 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
const fs = require('node:fs');
|
|
3
|
-
const path = require('node:path');
|
|
4
|
-
const { createHash } = require('node:crypto');
|
|
5
|
-
const RECEIPT = 'bundled-agent-sources.json';
|
|
6
|
-
const SCHEMA = 'blun.bundled-agent-sources/v1';
|
|
7
|
-
const PREFIXES = ['agent-spine-plugin/', 'standard-skills/translate-native/', 'standard-tools/language-guard/'];
|
|
8
|
-
const GUARD_RESPONSE_PROFILE = Object.freeze({
|
|
9
|
-
schema: 'blun.language-guard-response-profile/v1',
|
|
10
|
-
file: 'translate-native/scripts/blun_language_guard.py',
|
|
11
|
-
beforeSha256: '837361690535ded4251ca23fbab313f5c3a7005472c259aa8fc51ebf7ecb9cb9',
|
|
12
|
-
afterSha256: '1e21b570259de0145ace2abf55fb82bf8896c90aaae93839c97fff36a01731f2',
|
|
13
|
-
});
|
|
14
|
-
const GUARD_RESPONSE_PAYLOADS = Object.freeze([
|
|
15
|
-
'standard-skills/translate-native/scripts/blun_language_guard.py',
|
|
16
|
-
'standard-tools/language-guard/blun_language_guard.py',
|
|
17
|
-
]);
|
|
18
|
-
const REQUIRED = [
|
|
19
|
-
'agent-spine-plugin/blun.plugin.json', 'agent-spine-plugin/src/hook.js',
|
|
20
|
-
'agent-spine-plugin/src/mcp.js', 'agent-spine-plugin/skills/agent-spine/SKILL.md',
|
|
21
|
-
'standard-skills/translate-native/SKILL.md', ...GUARD_RESPONSE_PAYLOADS,
|
|
22
|
-
'standard-tools/language-guard/check_diacritics.py', 'standard-tools/language-guard/language_quality.py',
|
|
23
|
-
'standard-tools/language-guard/guard_service_client.py', 'standard-tools/language-guard/translation_guard.py',
|
|
24
|
-
];
|
|
25
|
-
const sha256 = bytes => createHash('sha256').update(bytes).digest('hex');
|
|
26
|
-
|
|
27
|
-
function payloadInventory(root, prefix = '') {
|
|
28
|
-
const files = {};
|
|
29
|
-
for (const name of fs.readdirSync(root).sort()) {
|
|
30
|
-
const target = path.join(root, name), relative = prefix + name;
|
|
31
|
-
const stat = fs.lstatSync(target);
|
|
32
|
-
if (stat.isSymbolicLink()) throw new Error('Linked bundled component: ' + relative);
|
|
33
|
-
if (stat.isDirectory()) Object.assign(files, payloadInventory(target, relative + '/'));
|
|
34
|
-
else if (stat.isFile()) files[relative] = sha256(fs.readFileSync(target));
|
|
35
|
-
else throw new Error('Unsupported bundled component: ' + relative);
|
|
36
|
-
}
|
|
37
|
-
return files;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
function verifyBundledAgentSources(root, pins) {
|
|
41
|
-
const receipt = JSON.parse(fs.readFileSync(path.join(root, RECEIPT), 'utf8'));
|
|
42
|
-
if (receipt.schema !== SCHEMA || !receipt.files || Array.isArray(receipt.files)) throw new Error('Invalid bundled source receipt');
|
|
43
|
-
for (const name of ['agentSpine', 'translateNative']) {
|
|
44
|
-
if (!/^[a-f0-9]{40}$/.test(receipt.sources?.[name]?.commit || '')) throw new Error('Missing bundled source commit: ' + name);
|
|
45
|
-
if (pins && JSON.stringify(receipt.sources[name]) !== JSON.stringify(pins[name])) throw new Error('Stale bundled source: ' + name);
|
|
46
|
-
}
|
|
47
|
-
for (const file of REQUIRED) if (!Object.hasOwn(receipt.files, file)) throw new Error('Missing required bundled source: ' + file);
|
|
48
|
-
const actual = {};
|
|
49
|
-
for (const prefix of PREFIXES) {
|
|
50
|
-
const target = path.join(root, prefix);
|
|
51
|
-
if (fs.lstatSync(target).isSymbolicLink()) throw new Error('Linked component directory');
|
|
52
|
-
Object.assign(actual, payloadInventory(target, prefix));
|
|
53
|
-
}
|
|
54
|
-
const expectedNames = Object.keys(receipt.files).sort();
|
|
55
|
-
if (JSON.stringify(Object.keys(actual).sort()) !== JSON.stringify(expectedNames)) throw new Error('Bundled source inventory mismatch');
|
|
56
|
-
for (const file of expectedNames) {
|
|
57
|
-
if (!/^[a-f0-9]{64}$/.test(receipt.files[file]) || actual[file] !== receipt.files[file]) throw new Error('Bundled source digest mismatch: ' + file);
|
|
58
|
-
}
|
|
59
|
-
// Bind the semantic repair independently of the self-reported file inventory.
|
|
60
|
-
const patch = receipt.localPatches?.guardResponseProfile;
|
|
61
|
-
if (!patch || typeof patch !== 'object' || Array.isArray(patch)
|
|
62
|
-
|| Object.keys(patch).length !== Object.keys(GUARD_RESPONSE_PROFILE).length
|
|
63
|
-
|| Object.entries(GUARD_RESPONSE_PROFILE).some(([key, value]) => patch[key] !== value)) {
|
|
64
|
-
throw new Error('Invalid language guard response profile repair receipt');
|
|
65
|
-
}
|
|
66
|
-
if (receipt.sourceDigests?.translateNative?.[GUARD_RESPONSE_PROFILE.file] !== GUARD_RESPONSE_PROFILE.beforeSha256) {
|
|
67
|
-
throw new Error('Invalid language guard response profile source digest');
|
|
68
|
-
}
|
|
69
|
-
for (const file of GUARD_RESPONSE_PAYLOADS) {
|
|
70
|
-
if (actual[file] !== GUARD_RESPONSE_PROFILE.afterSha256) {
|
|
71
|
-
throw new Error('Invalid language guard response profile payload: ' + file);
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
return receipt;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
if (require.main === module) {
|
|
78
|
-
try { const receipt = verifyBundledAgentSources(path.resolve(__dirname, '..'));
|
|
79
|
-
console.log(JSON.stringify({ verified: true, sources: receipt.sources, files: Object.keys(receipt.files).length }));
|
|
80
|
-
} catch (error) { console.error(error.message); process.exitCode = 1; }
|
|
81
|
-
}
|
|
82
|
-
module.exports = { verifyBundledAgentSources, payloadInventory, sha256, RECEIPT, SCHEMA, PREFIXES, REQUIRED,
|
|
83
|
-
GUARD_RESPONSE_PROFILE, GUARD_RESPONSE_PAYLOADS };
|
|
1
|
+
'use strict';
|
|
2
|
+
const fs = require('node:fs');
|
|
3
|
+
const path = require('node:path');
|
|
4
|
+
const { createHash } = require('node:crypto');
|
|
5
|
+
const RECEIPT = 'bundled-agent-sources.json';
|
|
6
|
+
const SCHEMA = 'blun.bundled-agent-sources/v1';
|
|
7
|
+
const PREFIXES = ['agent-spine-plugin/', 'standard-skills/translate-native/', 'standard-tools/language-guard/'];
|
|
8
|
+
const GUARD_RESPONSE_PROFILE = Object.freeze({
|
|
9
|
+
schema: 'blun.language-guard-response-profile/v1',
|
|
10
|
+
file: 'translate-native/scripts/blun_language_guard.py',
|
|
11
|
+
beforeSha256: '837361690535ded4251ca23fbab313f5c3a7005472c259aa8fc51ebf7ecb9cb9',
|
|
12
|
+
afterSha256: '1e21b570259de0145ace2abf55fb82bf8896c90aaae93839c97fff36a01731f2',
|
|
13
|
+
});
|
|
14
|
+
const GUARD_RESPONSE_PAYLOADS = Object.freeze([
|
|
15
|
+
'standard-skills/translate-native/scripts/blun_language_guard.py',
|
|
16
|
+
'standard-tools/language-guard/blun_language_guard.py',
|
|
17
|
+
]);
|
|
18
|
+
const REQUIRED = [
|
|
19
|
+
'agent-spine-plugin/blun.plugin.json', 'agent-spine-plugin/src/hook.js',
|
|
20
|
+
'agent-spine-plugin/src/mcp.js', 'agent-spine-plugin/skills/agent-spine/SKILL.md',
|
|
21
|
+
'standard-skills/translate-native/SKILL.md', ...GUARD_RESPONSE_PAYLOADS,
|
|
22
|
+
'standard-tools/language-guard/check_diacritics.py', 'standard-tools/language-guard/language_quality.py',
|
|
23
|
+
'standard-tools/language-guard/guard_service_client.py', 'standard-tools/language-guard/translation_guard.py',
|
|
24
|
+
];
|
|
25
|
+
const sha256 = bytes => createHash('sha256').update(bytes).digest('hex');
|
|
26
|
+
|
|
27
|
+
function payloadInventory(root, prefix = '') {
|
|
28
|
+
const files = {};
|
|
29
|
+
for (const name of fs.readdirSync(root).sort()) {
|
|
30
|
+
const target = path.join(root, name), relative = prefix + name;
|
|
31
|
+
const stat = fs.lstatSync(target);
|
|
32
|
+
if (stat.isSymbolicLink()) throw new Error('Linked bundled component: ' + relative);
|
|
33
|
+
if (stat.isDirectory()) Object.assign(files, payloadInventory(target, relative + '/'));
|
|
34
|
+
else if (stat.isFile()) files[relative] = sha256(fs.readFileSync(target));
|
|
35
|
+
else throw new Error('Unsupported bundled component: ' + relative);
|
|
36
|
+
}
|
|
37
|
+
return files;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function verifyBundledAgentSources(root, pins) {
|
|
41
|
+
const receipt = JSON.parse(fs.readFileSync(path.join(root, RECEIPT), 'utf8'));
|
|
42
|
+
if (receipt.schema !== SCHEMA || !receipt.files || Array.isArray(receipt.files)) throw new Error('Invalid bundled source receipt');
|
|
43
|
+
for (const name of ['agentSpine', 'translateNative']) {
|
|
44
|
+
if (!/^[a-f0-9]{40}$/.test(receipt.sources?.[name]?.commit || '')) throw new Error('Missing bundled source commit: ' + name);
|
|
45
|
+
if (pins && JSON.stringify(receipt.sources[name]) !== JSON.stringify(pins[name])) throw new Error('Stale bundled source: ' + name);
|
|
46
|
+
}
|
|
47
|
+
for (const file of REQUIRED) if (!Object.hasOwn(receipt.files, file)) throw new Error('Missing required bundled source: ' + file);
|
|
48
|
+
const actual = {};
|
|
49
|
+
for (const prefix of PREFIXES) {
|
|
50
|
+
const target = path.join(root, prefix);
|
|
51
|
+
if (fs.lstatSync(target).isSymbolicLink()) throw new Error('Linked component directory');
|
|
52
|
+
Object.assign(actual, payloadInventory(target, prefix));
|
|
53
|
+
}
|
|
54
|
+
const expectedNames = Object.keys(receipt.files).sort();
|
|
55
|
+
if (JSON.stringify(Object.keys(actual).sort()) !== JSON.stringify(expectedNames)) throw new Error('Bundled source inventory mismatch');
|
|
56
|
+
for (const file of expectedNames) {
|
|
57
|
+
if (!/^[a-f0-9]{64}$/.test(receipt.files[file]) || actual[file] !== receipt.files[file]) throw new Error('Bundled source digest mismatch: ' + file);
|
|
58
|
+
}
|
|
59
|
+
// Bind the semantic repair independently of the self-reported file inventory.
|
|
60
|
+
const patch = receipt.localPatches?.guardResponseProfile;
|
|
61
|
+
if (!patch || typeof patch !== 'object' || Array.isArray(patch)
|
|
62
|
+
|| Object.keys(patch).length !== Object.keys(GUARD_RESPONSE_PROFILE).length
|
|
63
|
+
|| Object.entries(GUARD_RESPONSE_PROFILE).some(([key, value]) => patch[key] !== value)) {
|
|
64
|
+
throw new Error('Invalid language guard response profile repair receipt');
|
|
65
|
+
}
|
|
66
|
+
if (receipt.sourceDigests?.translateNative?.[GUARD_RESPONSE_PROFILE.file] !== GUARD_RESPONSE_PROFILE.beforeSha256) {
|
|
67
|
+
throw new Error('Invalid language guard response profile source digest');
|
|
68
|
+
}
|
|
69
|
+
for (const file of GUARD_RESPONSE_PAYLOADS) {
|
|
70
|
+
if (actual[file] !== GUARD_RESPONSE_PROFILE.afterSha256) {
|
|
71
|
+
throw new Error('Invalid language guard response profile payload: ' + file);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return receipt;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (require.main === module) {
|
|
78
|
+
try { const receipt = verifyBundledAgentSources(path.resolve(__dirname, '..'));
|
|
79
|
+
console.log(JSON.stringify({ verified: true, sources: receipt.sources, files: Object.keys(receipt.files).length }));
|
|
80
|
+
} catch (error) { console.error(error.message); process.exitCode = 1; }
|
|
81
|
+
}
|
|
82
|
+
module.exports = { verifyBundledAgentSources, payloadInventory, sha256, RECEIPT, SCHEMA, PREFIXES, REQUIRED,
|
|
83
|
+
GUARD_RESPONSE_PROFILE, GUARD_RESPONSE_PAYLOADS };
|