deadbyte-mcp 0.15.34 → 0.15.36

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 (101) hide show
  1. package/MANIFEST.SHA256 +100 -38
  2. package/PROVENANCE.json +87 -41
  3. package/README.txt +72 -14
  4. package/RELEASE.json +187 -150
  5. package/SBOM.cdx.json +25 -3
  6. package/TEST-MATRIX.json +258 -151
  7. package/bridge/package.json +13 -0
  8. package/bridge/src/bridge-health-r59.mjs +52 -0
  9. package/bridge/src/bridge.mjs +487 -0
  10. package/bridge/src/node-http-adapter.mjs +56 -0
  11. package/bridge/src/public-tool-definitions.mjs +14 -0
  12. package/bridge/src/r40-governor.mjs +206 -0
  13. package/controller/deadbyte-controller.ps1 +152 -18
  14. package/controller/deadbyte-process-policy.json +4 -4
  15. package/docs/R59-TASK-CONTRACT.md +26 -0
  16. package/package.json +7 -3
  17. package/proof/r59-full-regression-current.json +13 -0
  18. package/proof/r59-full-regression-current.stderr.log +0 -0
  19. package/proof/r59-full-regression-current.stdout.log +1268 -0
  20. package/scripts/bridge-benchmark-r58.mjs +67 -0
  21. package/scripts/deploy-r48.mjs +2 -3
  22. package/scripts/gate-windows-r58.ps1 +33 -0
  23. package/scripts/gate-windows-r59-prepublish.ps1 +41 -0
  24. package/scripts/owner-probe-r58.mjs +31 -0
  25. package/scripts/performance-r58.mjs +57 -0
  26. package/scripts/public-baseline-r58.mjs +72 -0
  27. package/scripts/public-surface-r58.mjs +84 -0
  28. package/scripts/r58-cross-plane.ps1 +6 -0
  29. package/scripts/r58-focused.ps1 +6 -0
  30. package/scripts/r59-full-regression-capture.mjs +35 -0
  31. package/scripts/windows-gate-evidence-r58.mjs +93 -0
  32. package/src/capability-resolver.mjs +25 -7
  33. package/src/capability-snapshot.mjs +52 -0
  34. package/src/compact-gateway-mcp-tools.mjs +10 -11
  35. package/src/deadbyte-cli.mjs +40 -12
  36. package/src/doctor-foreground-r58.mjs +44 -0
  37. package/src/doctor-installation-r58.mjs +63 -0
  38. package/src/doctor-r45.mjs +21 -4
  39. package/src/doctor-runtime-r58.mjs +82 -0
  40. package/src/doctor-transactions-r58.mjs +40 -0
  41. package/src/foreground-process-identity.mjs +27 -0
  42. package/src/foreground-session-lock.mjs +20 -0
  43. package/src/foreground-session-lock.ps1 +15 -0
  44. package/src/mcp-server.mjs +179 -118
  45. package/src/r40-compact-disarmed-probe.mjs +1 -1
  46. package/src/r44-compact-disarmed-probe.mjs +1 -1
  47. package/src/r45-compact-disarmed-probe.mjs +1 -1
  48. package/src/release-generation.mjs +1 -0
  49. package/src/release-slot.mjs +2 -1
  50. package/src/remote-mcp-r40-parity-client.mjs +1 -1
  51. package/src/remote-mcp-r44-parity-client.mjs +1 -1
  52. package/src/remote-mcp-r45-parity-client.mjs +1 -1
  53. package/src/remote-mcp-smoke-client.mjs +1 -1
  54. package/src/remote-session-guardian.mjs +8 -1
  55. package/src/runtime-plane-registry.mjs +74 -0
  56. package/src/runtime-update.mjs +85 -2
  57. package/src/version.mjs +1 -1
  58. package/test/bridge-runtime-r59.test.mjs +60 -0
  59. package/test/capability-resolver.test.mjs +16 -0
  60. package/test/capability-snapshot-r59.test.mjs +39 -0
  61. package/test/compact-gateway-r36.test.mjs +22 -2
  62. package/test/compact-max-catalog-r44.test.mjs +1 -1
  63. package/test/compact-max-catalog-r45.test.mjs +1 -1
  64. package/test/contained.test.mjs +2 -1
  65. package/test/controller-policy-preflight-r59.test.mjs +39 -0
  66. package/test/controller-r211.test.mjs +4 -1
  67. package/test/controller.test.mjs +2 -1
  68. package/test/fixtures/r59-compact-surface-v0.15.35.json +222 -0
  69. package/test/helpers/r58-doctor-fixture.mjs +26 -0
  70. package/test/observation-integration.test.mjs +2 -3
  71. package/test/optional-plane-degraded-r59.test.mjs +44 -0
  72. package/test/r49-windows-gate.test.mjs +2 -2
  73. package/test/r57-release-contract.test.mjs +5 -3
  74. package/test/r58-bridge-dependency-binding.test.mjs +62 -0
  75. package/test/r58-bundled-bridge.test.mjs +24 -0
  76. package/test/r58-cleanup-retention.test.mjs +45 -0
  77. package/test/r58-completed-update-receipt.test.mjs +76 -0
  78. package/test/r58-concurrent-recovery.test.mjs +54 -0
  79. package/test/r58-controller-core-observation.test.mjs +36 -0
  80. package/test/r58-controller-json.test.mjs +48 -0
  81. package/test/r58-controller-live-local.test.mjs +98 -0
  82. package/test/r58-controller-readonly.test.mjs +23 -0
  83. package/test/r58-controller-state-coherence.test.mjs +45 -0
  84. package/test/r58-doctor-cli.test.mjs +20 -0
  85. package/test/r58-doctor-foreground.test.mjs +50 -0
  86. package/test/r58-doctor-installation.test.mjs +55 -0
  87. package/test/r58-doctor-pointer.test.mjs +41 -0
  88. package/test/r58-doctor-runtime.test.mjs +45 -0
  89. package/test/r58-doctor-transactions.test.mjs +21 -0
  90. package/test/r58-guardian-channel.test.mjs +26 -0
  91. package/test/r58-launcher-shell.test.mjs +28 -0
  92. package/test/r58-owner-identity.test.mjs +20 -0
  93. package/test/r58-release-contract.test.mjs +50 -0
  94. package/test/r58-session-lock.test.mjs +26 -0
  95. package/test/r58-stale-owner.test.mjs +71 -0
  96. package/test/r58-windows-gate.test.mjs +32 -0
  97. package/test/r59-release-contract.test.mjs +61 -0
  98. package/test/r59-tool-surface-compat.test.mjs +90 -0
  99. package/test/release-generation.test.mjs +4 -1
  100. package/test/remote-session-cli.test.mjs +55 -2
  101. package/test/runtime-plane-registry-r59.test.mjs +40 -0
@@ -0,0 +1,487 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { createServer } from 'node:http';
3
+ import { readFileSync } from 'node:fs';
4
+ import os from 'node:os';
5
+ import path from 'node:path';
6
+ import { Client } from '@modelcontextprotocol/client';
7
+ import { StdioClientTransport, getDefaultEnvironment } from '@modelcontextprotocol/client/stdio';
8
+ import { McpServer, createMcpHandler, createRequestStateCodec, inputRequired, inputResponse } from '@modelcontextprotocol/server';
9
+ import {preparePublicToolDefinitions} from './public-tool-definitions.mjs';
10
+ import {createDeadbyteNodeHandler} from './node-http-adapter.mjs';
11
+ import { bridgeCatalogSha256, bridgeDeadlineForTool, createBridgeGovernor, enforceDesktopBridgeResult, verifyCompactCatalogAttestation } from './r40-governor.mjs';
12
+ import { createBridgeHealth } from './bridge-health-r59.mjs';
13
+
14
+ const coreRoot = path.resolve(process.env.DEADBYTE_CORE_ROOT ?? 'C:/Users/DEADBYTE/Downloads/deadbyte-mcp-v0.6.0');
15
+ const coreServer = path.join(coreRoot, 'src', 'mcp-server.mjs');
16
+ const workspaceRoot = path.resolve(process.env.DEADBYTE_WORKSPACE_ROOT ?? path.join(coreRoot, 'workspaces'));
17
+ const port = Number(process.env.DEADBYTE_BRIDGE_PORT ?? '8787');
18
+ const baseTools = ['contained_transform', 'evidence_verify', 'file_read', 'proof_execute', 'workspace_open'];
19
+ const privilegedTools = ['host_exec', 'host_file_read', 'host_file_write'];
20
+ const agentTools = [
21
+ 'agent_task_approve', 'agent_task_cancel', 'agent_task_events', 'agent_task_run',
22
+ 'agent_task_status', 'agent_task_submit', 'agent_task_verify'
23
+ ];
24
+ const codingTools = [
25
+ 'coding_benchmark_run', 'coding_command_run', 'coding_file_read', 'coding_file_write',
26
+ 'coding_receipt_verify', 'coding_session_status', 'coding_task_approve', 'coding_task_cancel',
27
+ 'coding_task_events', 'coding_task_run', 'coding_task_status', 'coding_task_submit',
28
+ 'coding_task_verify', 'coding_text_replace', 'coding_tree'
29
+ ];
30
+ const autonomousTools = [
31
+ 'autonomous_goal_approve', 'autonomous_goal_cancel', 'autonomous_goal_events',
32
+ 'autonomous_goal_run', 'autonomous_goal_status', 'autonomous_goal_submit',
33
+ 'autonomous_goal_verify', 'autonomous_release_gate', 'autonomous_session_status',
34
+ 'native_diff_apply', 'native_diff_preview', 'native_diff_verify'
35
+ ];
36
+ const r35FullTools = [
37
+ 'agent_task_approve','agent_task_cancel','agent_task_events','agent_task_run','agent_task_status','agent_task_submit','agent_task_verify',
38
+ 'autonomous_goal_approve','autonomous_goal_cancel','autonomous_goal_events','autonomous_goal_run','autonomous_goal_status','autonomous_goal_submit','autonomous_goal_verify','autonomous_release_gate','autonomous_session_status',
39
+ 'coding_benchmark_run','coding_command_run','coding_file_read','coding_file_write','coding_files_read','coding_receipt_verify','coding_search','coding_session_status','coding_stat',
40
+ 'coding_symbol_definition','coding_symbol_outline','coding_symbol_references','coding_symbol_replace',
41
+ 'coding_task_approve','coding_task_cancel','coding_task_events','coding_task_run','coding_task_status','coding_task_submit','coding_task_verify','coding_text_replace','coding_tree',
42
+ 'contained_transform','evidence_verify','file_read',
43
+ 'host_exec','host_file_read','host_file_write',
44
+ 'machine_fs_delete','machine_fs_edit_exact','machine_fs_list','machine_fs_mkdir','machine_fs_move','machine_fs_read','machine_fs_stat','machine_fs_write',
45
+ 'machine_list','machine_ping','machine_process_list','machine_revoke','machine_search_cancel','machine_search_page','machine_search_start','machine_search_status','machine_status','machine_system_status',
46
+ 'native_diff_apply','native_diff_preview','native_diff_verify',
47
+ 'process_session_action','process_session_list','process_session_read','process_session_start','process_session_status','process_session_terminate',
48
+ 'proof_execute','workspace_open'
49
+ ].sort();
50
+ const r36CompactTools = [
51
+ 'autonomous_goal_approve','autonomous_goal_cancel','autonomous_goal_events','autonomous_goal_run','autonomous_goal_status','autonomous_goal_submit','autonomous_goal_verify','autonomous_release_gate','autonomous_session_status',
52
+ 'capability_manifest',
53
+ 'coding_benchmark_run','coding_command_run','coding_file_read','coding_file_write','coding_inspect','coding_receipt_verify','coding_session_status','coding_symbol_replace',
54
+ 'coding_task_approve','coding_task_cancel','coding_task_events','coding_task_run','coding_task_status','coding_task_submit','coding_task_verify','coding_text_replace','coding_tree',
55
+ 'evidence_verify',
56
+ 'host_exec','host_file_read','host_file_write',
57
+ 'machine_fs','machine_list','machine_observe','machine_ping','machine_revoke','machine_status',
58
+ 'native_diff_apply','native_diff_preview','native_diff_verify',
59
+ 'process_session'
60
+ ].sort();
61
+ if (r35FullTools.length !== 73) throw new Error(`R35 production catalog constant drift: ${r35FullTools.length}`);
62
+ if (r36CompactTools.length !== 41) throw new Error(`R36 compact catalog constant drift: ${r36CompactTools.length}`);
63
+ const AUTONOMOUS_DOWNSTREAM_TIMEOUT_MS = 900000;
64
+ const AUTONOMOUS_DOWNSTREAM_TOTAL_MS = 1200000;
65
+ const AUDIT_MAX_STRING_BYTES = 512;
66
+ const AUDIT_MAX_ARRAY_ITEMS = 24;
67
+ const AUDIT_MAX_OBJECT_KEYS = 48;
68
+ const SENSITIVE_AUDIT_KEY = /(token|secret|password|authorization|cookie|recovery|private[_-]?key)/i;
69
+ const PAYLOAD_AUDIT_KEY = /^(content|raw_text|text)$/i;
70
+ let auditSequence = 0;
71
+ const bridgeGovernor = createBridgeGovernor();
72
+ const bridgeHealth=createBridgeHealth({maxCapabilityAgeMs:15000});
73
+
74
+ function auditDigest(text) {
75
+ return createHash('sha256').update(text, 'utf8').digest('hex');
76
+ }
77
+
78
+ function auditString(value, key = '') {
79
+ const bytes = Buffer.byteLength(value, 'utf8');
80
+ if (SENSITIVE_AUDIT_KEY.test(key)) return `<redacted bytes=${bytes} sha256=${auditDigest(value)}>`;
81
+ if (PAYLOAD_AUDIT_KEY.test(key)) return `<payload bytes=${bytes} sha256=${auditDigest(value)}>`;
82
+ if (/^(?:Bearer\s+|Basic\s+|sk-[A-Za-z0-9_-]{12,}|gh[pousr]_[A-Za-z0-9_]{12,})/i.test(value)) {
83
+ return `<redacted bytes=${bytes} sha256=${auditDigest(value)}>`;
84
+ }
85
+ if (bytes <= AUDIT_MAX_STRING_BYTES) return value;
86
+ const prefix = value.slice(0, 256);
87
+ return `${prefix}<truncated bytes=${bytes} sha256=${auditDigest(value)}>`;
88
+ }
89
+
90
+ function auditSanitize(value, key = '', depth = 0) {
91
+ if (value === null || value === undefined) return value ?? null;
92
+ if (depth > 6) return '<depth-limit>';
93
+ if (typeof value === 'string') return auditString(value, key);
94
+ if (typeof value === 'number' || typeof value === 'boolean') return value;
95
+ if (typeof value === 'bigint') return value.toString();
96
+ if (Array.isArray(value)) {
97
+ const out = value.slice(0, AUDIT_MAX_ARRAY_ITEMS).map(item => auditSanitize(item, key, depth + 1));
98
+ if (value.length > AUDIT_MAX_ARRAY_ITEMS) out.push(`<${value.length - AUDIT_MAX_ARRAY_ITEMS} more items>`);
99
+ return out;
100
+ }
101
+ if (typeof value === 'object') {
102
+ const entries = Object.entries(value).slice(0, AUDIT_MAX_OBJECT_KEYS);
103
+ const out = {};
104
+ for (const [childKey, childValue] of entries) out[childKey] = auditSanitize(childValue, childKey, depth + 1);
105
+ if (Object.keys(value).length > AUDIT_MAX_OBJECT_KEYS) out._truncated_keys = Object.keys(value).length - AUDIT_MAX_OBJECT_KEYS;
106
+ return out;
107
+ }
108
+ return String(value);
109
+ }
110
+
111
+ const BRIDGE_LOG_LEVEL = process.env.DEADBYTE_BRIDGE_LOG_LEVEL === 'debug' ? 'debug' : 'human';
112
+
113
+ function auditWrite(kind, call, payload) {
114
+ const stamp = new Date().toISOString();
115
+ const id = String(call.id).padStart(6, '0');
116
+ console.log(`[TOOL ${kind}] ${stamp} #${id} ${call.name} ${JSON.stringify(auditSanitize(payload))}`);
117
+ }
118
+
119
+ function compactDuration(ms) {
120
+ if (!Number.isFinite(ms) || ms < 0) return '0ms';
121
+ if (ms < 10) return `${ms.toFixed(1)}ms`;
122
+ if (ms < 1000) return `${Math.round(ms)}ms`;
123
+ return `${(ms / 1000).toFixed(ms < 10000 ? 2 : 1)}s`;
124
+ }
125
+
126
+ function auditShortReason(result, error = null) {
127
+ const structured = result?.structuredContent;
128
+ const candidates = error
129
+ ? [error?.message ?? String(error)]
130
+ : [
131
+ structured?.error, structured?.message, structured?.reason,
132
+ structured?.result?.error, structured?.result?.message,
133
+ ...(Array.isArray(result?.content) ? result.content.filter(item => item?.type === 'text').map(item => item.text) : [])
134
+ ];
135
+ for (const candidate of candidates) {
136
+ if (typeof candidate !== 'string' || !candidate) continue;
137
+ const sanitized = auditString(candidate);
138
+ const text = String(sanitized).replace(/[\r\n\t]+/g, ' ').replace(/\s{2,}/g, ' ').trim();
139
+ if (text) return text.length <= 180 ? text : `${text.slice(0, 179)}…`;
140
+ }
141
+ return '';
142
+ }
143
+
144
+ function auditCompactResult(call, { result = null, error = null } = {}) {
145
+ const elapsedMs = Number(process.hrtime.bigint() - call.started) / 1e6;
146
+ const failed = error !== null || result?.isError === true;
147
+ const reason = failed ? auditShortReason(result, error) : '';
148
+ const stamp = new Date().toISOString();
149
+ const id = String(call.id).padStart(6, '0');
150
+ console.log(`[TOOL] ${stamp} #${id} ${failed ? '✗' : '✓'} ${call.name} ${compactDuration(elapsedMs)}${reason ? ` — ${reason}` : ''}`);
151
+ return elapsedMs;
152
+ }
153
+
154
+ function auditToolStart(name, args) {
155
+ const call = { id: ++auditSequence, name, started: process.hrtime.bigint() };
156
+ if (BRIDGE_LOG_LEVEL === 'debug') auditWrite('IN ', call, args);
157
+ return call;
158
+ }
159
+
160
+ function auditToolFinish(call, result) {
161
+ const elapsedMs = auditCompactResult(call, { result });
162
+ if (BRIDGE_LOG_LEVEL === 'debug') {
163
+ auditWrite('OUT', call, {
164
+ elapsed_ms: Number(elapsedMs.toFixed(3)),
165
+ is_error: result?.isError === true,
166
+ structured: result?.structuredContent ?? null,
167
+ content: result?.content ?? null
168
+ });
169
+ }
170
+ }
171
+
172
+ function auditToolFail(call, error) {
173
+ const elapsedMs = auditCompactResult(call, { error });
174
+ if (BRIDGE_LOG_LEVEL === 'debug') {
175
+ auditWrite('ERR', call, {
176
+ elapsed_ms: Number(elapsedMs.toFixed(3)),
177
+ name: error?.name ?? 'Error',
178
+ message: error?.message ?? String(error)
179
+ });
180
+ }
181
+ }
182
+
183
+ const allowedCatalogs = [];
184
+ for (const host of [false, true]) {
185
+ for (const agent of [false, true]) {
186
+ for (const coding of [false, true]) {
187
+ for (const autonomous of [false, true]) {
188
+ if (autonomous && !coding) continue;
189
+ const items = [...baseTools];
190
+ if (host) items.push(...privilegedTools);
191
+ if (agent) items.push(...agentTools);
192
+ if (coding) items.push(...codingTools);
193
+ if (autonomous) items.push(...autonomousTools);
194
+ allowedCatalogs.push(items.sort());
195
+ }
196
+ }
197
+ }
198
+ }
199
+
200
+ function loadToken() {
201
+ const fromEnv = process.env.DEADBYTE_BRIDGE_PATH_TOKEN ?? '';
202
+ if (/^[a-f0-9]{64}$/.test(fromEnv)) return fromEnv;
203
+ const tokenFile = process.env.DEADBYTE_BRIDGE_TOKEN_FILE ?? path.join(os.homedir(), '.deadbyte-mcp', 'bridge', 'path-token.txt');
204
+ const value = readFileSync(tokenFile, 'utf8').trim();
205
+ if (!/^[a-f0-9]{64}$/.test(value)) throw new Error(`invalid bridge path token file: ${tokenFile}`);
206
+ return value;
207
+ }
208
+
209
+ const token = loadToken();
210
+ const requestStateKey = createHash('sha256')
211
+ .update('DEADBYTE-R24-MRTR-V1\0', 'utf8')
212
+ .update(token, 'utf8')
213
+ .digest();
214
+ const requestStateCodec = createRequestStateCodec({ key: requestStateKey, ttlSeconds: 600 });
215
+ if (!Number.isInteger(port) || port < 1024 || port > 65535) throw new Error('invalid DEADBYTE_BRIDGE_PORT');
216
+ const endpointPath = `/mcp/${token}`;
217
+
218
+ process.on('uncaughtExceptionMonitor', error => {
219
+ console.error(`[bridge-fatal-monitor] ${error?.stack ?? String(error)}`);
220
+ });
221
+ process.on('exit', code => {
222
+ console.error(`[bridge-exit] code=${code}`);
223
+ });
224
+
225
+ function newDownstreamClient(){return new Client(
226
+ {name:'deadbyte-chatgpt-bridge',version:'0.2.0'},
227
+ {versionNegotiation:{mode:{pin:'2026-07-28'}}}
228
+ );}
229
+ function newDownstreamTransport(){return new StdioClientTransport({command:process.execPath,args:[coreServer],cwd:coreRoot,
230
+ env:{...getDefaultEnvironment(),...process.env,DEADBYTE_WORKSPACE_ROOT:workspaceRoot},stderr:'pipe'});}
231
+ function pipeCoreStderr(transport){transport.stderr?.on('data',chunk=>process.stderr.write(`[deadbyte-core] ${chunk}`));}
232
+
233
+ let downstream=newDownstreamClient();
234
+ let downstreamTransport=newDownstreamTransport();
235
+
236
+ pipeCoreStderr(downstreamTransport);
237
+ await downstream.connect(downstreamTransport);
238
+ if (downstream.getProtocolEra() !== 'modern') throw new Error('DEADBYTE downstream is not modern MCP');
239
+
240
+ const listed = await downstream.listTools();
241
+ const actualNames = listed.tools.map(tool => tool.name).sort();
242
+ const exactR36Compact = JSON.stringify(actualNames) === JSON.stringify(r36CompactTools);
243
+ let compactCatalogAttestation = null;
244
+ let compactCatalogLegacy = false;
245
+ let currentCapabilityManifest = null;
246
+ if (actualNames.includes('capability_manifest')) {
247
+ const manifestResult = await downstream.callTool(
248
+ { name:'capability_manifest', arguments:{} },
249
+ bridgeDeadlineForTool('capability_manifest')
250
+ );
251
+ const manifestSchema = manifestResult?.structuredContent?.result?.schema ?? null;
252
+ if (manifestSchema === 'deadbyte.capability-manifest.v2' || manifestSchema === 'deadbyte.capability-manifest.v3') {
253
+ compactCatalogAttestation = verifyCompactCatalogAttestation(actualNames, manifestResult);
254
+ if(manifestSchema==='deadbyte.capability-manifest.v3') currentCapabilityManifest=manifestResult.structuredContent.result;
255
+ } else if (manifestSchema === 'deadbyte.capability-manifest.v1' && exactR36Compact) {
256
+ compactCatalogLegacy = true;
257
+ } else {
258
+ throw new Error(`DEADBYTE compact catalog attestation unavailable or invalid schema: ${String(manifestSchema)}`);
259
+ }
260
+ }
261
+ const catalogOk = compactCatalogAttestation?.ok === true || compactCatalogLegacy ||
262
+ allowedCatalogs.some(expected => JSON.stringify(actualNames) === JSON.stringify(expected)) ||
263
+ JSON.stringify(actualNames) === JSON.stringify(r35FullTools);
264
+ if (!catalogOk) throw new Error(`DEADBYTE tool catalog mismatch: ${JSON.stringify(actualNames)}`);
265
+ const hostConfigured = privilegedTools.every(name => actualNames.includes(name));
266
+ const agentConfigured = agentTools.every(name => actualNames.includes(name));
267
+ const codingConfigured = codingTools.every(name => actualNames.includes(name));
268
+ const autonomousConfigured = autonomousTools.every(name => actualNames.includes(name));
269
+ const bridgeCatalogDigest = bridgeCatalogSha256(actualNames);
270
+ const coreIdentity=createHash('sha256').update(readFileSync(coreServer)).digest('hex');
271
+ bridgeHealth.coreConnected({childIdentity:coreIdentity,protocolEra:downstream.getProtocolEra(),catalogSha256:bridgeCatalogDigest});
272
+ if(currentCapabilityManifest) bridgeHealth.capabilityObserved(currentCapabilityManifest);
273
+ else bridgeHealth.degraded('capability manifest v3 is required for fresh readiness');
274
+
275
+ async function refreshCapabilityAttestation(){
276
+ if(reconnecting||!bridgeHealth.snapshot().core)return;
277
+ try{
278
+ const result=await downstream.callTool({name:'capability_manifest',arguments:{}},bridgeDeadlineForTool('capability_manifest'));
279
+ verifyCompactCatalogAttestation(actualNames,result);
280
+ bridgeHealth.capabilityObserved(result.structuredContent.result);
281
+ }catch(error){bridgeHealth.degraded(`capability refresh failed: ${error?.message??String(error)}`);}
282
+ }
283
+ const capabilityRefreshTimer=setInterval(()=>{void refreshCapabilityAttestation();},4000);
284
+ capabilityRefreshTimer.unref?.();
285
+ let shuttingDown=false;
286
+ let reconnecting=false;
287
+ let downstreamGeneration=1;
288
+ const reconnectDelays=[250,500,1000,2000,4000];
289
+ const wait=ms=>new Promise(resolve=>setTimeout(resolve,ms));
290
+
291
+ async function attestReplacement(client){
292
+ if(client.getProtocolEra()!=='modern') throw new Error('replacement core protocol is not modern');
293
+ const replacement=(await client.listTools()).tools.map(tool=>tool.name).sort();
294
+ if(JSON.stringify(replacement)!==JSON.stringify(actualNames)) throw new Error('replacement core catalog drift');
295
+ const result=await client.callTool({name:'capability_manifest',arguments:{}},bridgeDeadlineForTool('capability_manifest'));
296
+ verifyCompactCatalogAttestation(actualNames,result);
297
+ if(result?.structuredContent?.result?.schema!=='deadbyte.capability-manifest.v3') throw new Error('replacement core capability manifest v3 missing');
298
+ return result.structuredContent.result;
299
+ }
300
+ function bindDownstreamClose(transport){
301
+ const sdkClose=transport.onclose;
302
+ transport.onclose=()=>{
303
+ sdkClose?.();
304
+ if(shuttingDown)return;
305
+ bridgeHealth.reconciling(`downstream core generation ${downstreamGeneration} closed`);
306
+ bridgeGovernor.reconcile('downstream core unavailable before dispatch');
307
+ void reconcileDownstream();
308
+ };
309
+ }
310
+ async function reconcileDownstream(){
311
+ if(reconnecting||shuttingDown)return;
312
+ reconnecting=true;
313
+ try{
314
+ for(let attempt=0;attempt<reconnectDelays.length&&!shuttingDown;attempt++){
315
+ await wait(reconnectDelays[attempt]);
316
+ const nextClient=newDownstreamClient(),nextTransport=newDownstreamTransport();
317
+ pipeCoreStderr(nextTransport);
318
+ try{
319
+ if(createHash('sha256').update(readFileSync(coreServer)).digest('hex')!==coreIdentity) throw new Error('core script identity drift');
320
+ await nextClient.connect(nextTransport);
321
+ const manifest=await attestReplacement(nextClient);
322
+ downstream=nextClient;downstreamTransport=nextTransport;downstreamGeneration++;
323
+ bindDownstreamClose(nextTransport);
324
+ bridgeHealth.coreConnected({childIdentity:coreIdentity,protocolEra:nextClient.getProtocolEra(),catalogSha256:bridgeCatalogDigest});
325
+ bridgeHealth.capabilityObserved(manifest);
326
+ return;
327
+ }catch(error){
328
+ await nextClient.close().catch(()=>{});
329
+ bridgeHealth.reconciling(`core reconnect ${attempt+1}/${reconnectDelays.length} failed: ${error?.message??String(error)}`);
330
+ }
331
+ }
332
+ bridgeHealth.degraded('core restart budget exhausted; bridge quarantined');
333
+ }finally{reconnecting=false;}
334
+ }
335
+ bindDownstreamClose(downstreamTransport);
336
+
337
+ function plannerTextFromInput(ctx) {
338
+ const response = inputResponse(ctx.mcpReq.inputResponses, 'planner');
339
+ if (!response || response.kind !== 'sampling') throw new Error('planner sampling response missing');
340
+ const content = response.result?.content;
341
+ const block = Array.isArray(content) ? content.find(item => item?.type === 'text') : content;
342
+ if (!block || block.type !== 'text' || typeof block.text !== 'string') throw new Error('planner sampling response missing text');
343
+ return { raw_text: block.text, model: typeof response.result?.model === 'string' ? response.result.model.slice(0, 128) : 'client-model' };
344
+ }
345
+
346
+ const publicToolDefinitions=preparePublicToolDefinitions(listed.tools);
347
+
348
+ function buildFrontServer() {
349
+ const front = new McpServer(
350
+ { name: 'deadbyte-chatgpt-bridge', version: '0.2.0' },
351
+ { requestState: { verify: requestStateCodec.verify } }
352
+ );
353
+ for (const tool of publicToolDefinitions) {
354
+ const {inputSchema,outputSchema}=tool;
355
+ front.registerTool(tool.name, {
356
+ title: tool.title,
357
+ description: tool.description,
358
+ inputSchema,
359
+ outputSchema,
360
+ annotations: tool.annotations
361
+ }, async (args, ctx) => {
362
+ const currentHealth=bridgeHealth.snapshot();
363
+ if(!currentHealth.core||['STARTING','RECONCILING','STOPPING'].includes(currentHealth.state)) throw new Error(`bridge correlation generation=${downstreamGeneration}; downstream ${currentHealth.state.toLowerCase()}; request not dispatched`);
364
+ if (tool.name !== 'autonomous_goal_run') {
365
+ const audit = auditToolStart(tool.name, args);
366
+ const ticket = await bridgeGovernor.acquire(tool.name,{signal:ctx.mcpReq.signal});
367
+ try {
368
+ const budget = bridgeDeadlineForTool(tool.name);
369
+ const result = await downstream.callTool({ name: tool.name, arguments: args }, budget);
370
+ enforceDesktopBridgeResult(tool.name, result);
371
+ auditToolFinish(audit, result);
372
+ return result;
373
+ } catch (error) {
374
+ auditToolFail(audit, error);
375
+ if(tool.annotations?.readOnlyHint!==true&&['RECONCILING','DEGRADED'].includes(bridgeHealth.snapshot().state)) throw new Error(`bridge correlation call=${audit.id} generation=${downstreamGeneration}; mutation outcome unknown; reconcile before retry`,{cause:error});
376
+ throw error;
377
+ } finally {
378
+ ticket.release();
379
+ }
380
+ }
381
+ const state = ctx.mcpReq.requestState();
382
+ const callArgs = { ...args };
383
+ if (state) {
384
+ if (state.goal_id !== args.goal_id || typeof state.request_id !== 'string') throw new Error('autonomous planner requestState binding mismatch');
385
+ const planner = plannerTextFromInput(ctx);
386
+ callArgs.planner_response = { request_id: state.request_id, raw_text: planner.raw_text, model: planner.model };
387
+ }
388
+ const audit = auditToolStart(tool.name, callArgs);
389
+ const ticket = await bridgeGovernor.acquire(tool.name,{signal:ctx.mcpReq.signal});
390
+ let result;
391
+ try {
392
+ const budget = bridgeDeadlineForTool(tool.name);
393
+ result = await downstream.callTool({ name: tool.name, arguments: callArgs }, budget);
394
+ auditToolFinish(audit, result);
395
+ } catch (error) {
396
+ auditToolFail(audit, error);
397
+ if(['RECONCILING','DEGRADED'].includes(bridgeHealth.snapshot().state)) throw new Error(`bridge correlation call=${audit.id} generation=${downstreamGeneration}; autonomous outcome unknown; reconcile before retry`,{cause:error});
398
+ throw error;
399
+ } finally {
400
+ ticket.release();
401
+ }
402
+ const plannerRequest = result?.structuredContent?.status === 'input_required' ? result.structuredContent.planner_request : null;
403
+ if (!plannerRequest) return result;
404
+ if (plannerRequest.request_id === undefined || typeof plannerRequest.prompt !== 'string') throw new Error('autonomous planner request malformed');
405
+ return inputRequired({
406
+ inputRequests: {
407
+ planner: inputRequired.createMessage({
408
+ messages: [{ role: 'user', content: { type: 'text', text: plannerRequest.prompt } }],
409
+ maxTokens: 4096,
410
+ temperature: 0,
411
+ includeContext: 'none'
412
+ })
413
+ },
414
+ requestState: await requestStateCodec.mint({ goal_id: args.goal_id, request_id: plannerRequest.request_id })
415
+ });
416
+ });
417
+ }
418
+ return front;
419
+ }
420
+
421
+ const mcpHandler = createMcpHandler(() => buildFrontServer(), {
422
+ legacy: 'reject',
423
+ responseMode: 'json',
424
+ onerror: error => console.error(`[mcp-http] ${error.stack ?? String(error)}`)
425
+ });
426
+ const nodeMcpHandler = createDeadbyteNodeHandler(mcpHandler);
427
+ const httpServer = createServer(async (req, res) => {
428
+ try {
429
+ const pathname = new URL(req.url ?? '/', 'http://127.0.0.1').pathname;
430
+ if (pathname === '/healthz') {
431
+ const health=bridgeHealth.snapshot();
432
+ res.writeHead(health.http_status, { 'content-type': 'application/json; charset=utf-8','cache-control':'no-store' });
433
+ return res.end(JSON.stringify(health));
434
+ }
435
+ if (pathname === '/healthz/r40') {
436
+ const health=bridgeHealth.snapshot();
437
+ res.writeHead(health.http_status, { 'content-type': 'application/json; charset=utf-8', 'cache-control': 'no-store' });
438
+ return res.end(JSON.stringify({
439
+ status:health.ready?'ok':'degraded',runtime:health,catalog_sha256:bridgeCatalogDigest,
440
+ catalog_attestation:compactCatalogAttestation?.ok===true?'verified':compactCatalogLegacy?'legacy-r39':'not-applicable',
441
+ ledger_sha256:compactCatalogAttestation?.ledger_sha256??null,
442
+ governor:bridgeGovernor.snapshot()
443
+ }));
444
+ }
445
+ if (pathname !== endpointPath) {
446
+ res.writeHead(404, { 'content-type': 'text/plain; charset=utf-8' });
447
+ return res.end('not found');
448
+ }
449
+ const contentLength = Number(req.headers['content-length'] ?? '0');
450
+ if (Number.isFinite(contentLength) && contentLength > 1024 * 1024) {
451
+ res.writeHead(413, { 'content-type': 'text/plain; charset=utf-8' });
452
+ return res.end('request too large');
453
+ }
454
+ await nodeMcpHandler(req, res);
455
+ } catch (error) {
456
+ if (!res.headersSent) res.writeHead(500, { 'content-type': 'text/plain; charset=utf-8' });
457
+ if (!res.writableEnded) res.end('bridge error');
458
+ console.error(error instanceof Error ? error.stack : String(error));
459
+ }
460
+ });
461
+
462
+ await new Promise((resolve, reject) => {
463
+ httpServer.once('error', reject);
464
+ httpServer.listen(port, '127.0.0.1', resolve);
465
+ });
466
+ console.log(`BRIDGE_READY http://127.0.0.1:${port}${endpointPath}`);
467
+ console.log(`BRIDGE_TOOLS ${actualNames.join(',')}`);
468
+ console.log(`BRIDGE_CATALOG_SHA256 ${bridgeCatalogDigest}`);
469
+ console.log(`BRIDGE_CATALOG_ATTESTATION ${compactCatalogAttestation?.ok===true?'VERIFIED':compactCatalogLegacy?'LEGACY_R39':'NOT_APPLICABLE'}`);
470
+ console.log(`BRIDGE_HOST_PLANE ${hostConfigured ? 'CONFIGURED_DISARMED_OR_ARMED_BY_LOCAL_LEASE' : 'UNCONFIGURED'}`);
471
+ console.log(`BRIDGE_AGENT_RUNTIME ${agentConfigured ? 'ENABLED' : 'DISABLED'}`);
472
+ console.log(`BRIDGE_CODING_RUNTIME ${codingConfigured ? 'ENABLED' : 'DISABLED'}`);
473
+ console.log(`BRIDGE_AUTONOMOUS_RUNTIME ${autonomousConfigured ? 'ENABLED' : 'DISABLED'}`);
474
+
475
+ async function shutdown() {
476
+ shuttingDown=true;
477
+ bridgeHealth.stopping();
478
+ clearInterval(capabilityRefreshTimer);
479
+ httpServer.close();
480
+ await Promise.race([bridgeGovernor.close('bridge stopping'),new Promise(resolve=>setTimeout(resolve,5000))]);
481
+ await mcpHandler.close().catch(() => {});
482
+ await downstream.close().catch(() => {});
483
+ process.exit(0);
484
+ }
485
+ process.once('SIGINT', shutdown);
486
+ process.once('SIGTERM', shutdown);
487
+
@@ -0,0 +1,56 @@
1
+ import {once} from 'node:events';
2
+
3
+ function requestHeaders(headers){
4
+ const out=new Headers();
5
+ for(const [name,value] of Object.entries(headers??{})){
6
+ if(value===undefined) continue;
7
+ if(Array.isArray(value)){for(const item of value) out.append(name,String(item));}
8
+ else out.append(name,String(value));
9
+ }
10
+ return out;
11
+ }
12
+ async function requestBody(req,maxBodyBytes){
13
+ const method=String(req.method??'GET').toUpperCase();
14
+ if(method==='GET'||method==='HEAD') return undefined;
15
+ const chunks=[];let total=0;
16
+ for await(const chunk of req){
17
+ const bytes=Buffer.isBuffer(chunk)?chunk:Buffer.from(chunk);
18
+ total+=bytes.length;
19
+ if(total>maxBodyBytes) throw new Error(`bridge request body exceeds ${maxBodyBytes} bytes`);
20
+ chunks.push(bytes);
21
+ }
22
+ return Buffer.concat(chunks,total);
23
+ }
24
+ async function writeResponse(res,response){
25
+ const headers={};
26
+ for(const [name,value] of response.headers.entries()) headers[name]=value;
27
+ res.writeHead(response.status,headers);
28
+ if(!response.body){res.end();return;}
29
+ const reader=response.body.getReader();
30
+ try{
31
+ while(true){
32
+ const {done,value}=await reader.read();
33
+ if(done) break;
34
+ if(res.destroyed){await reader.cancel().catch(()=>{});return;}
35
+ if(!res.write(value)) await once(res,'drain');
36
+ }
37
+ }finally{reader.releaseLock();}
38
+ if(!res.destroyed) res.end();
39
+ }
40
+
41
+ export function createDeadbyteNodeHandler(handler,{maxBodyBytes=1024*1024}={}){
42
+ if(!handler||typeof handler.fetch!=='function') throw new Error('bridge node adapter requires fetch-shaped MCP handler');
43
+ if(!Number.isInteger(maxBodyBytes)||maxBodyBytes<1024||maxBodyBytes>4*1024*1024) throw new Error('bridge node adapter maxBodyBytes invalid');
44
+ return async function nodeHandler(req,res){
45
+ const method=String(req.method??'GET').toUpperCase();
46
+ const url=new URL(String(req.url??'/'),'http://127.0.0.1');
47
+ const body=await requestBody(req,maxBodyBytes);
48
+ const init={method,headers:requestHeaders(req.headers)};
49
+ if(body!==undefined) init.body=body;
50
+ const request=new Request(url,init);
51
+ const options=req.auth?{authInfo:req.auth}:undefined;
52
+ const response=await handler.fetch(request,options);
53
+ if(!(response instanceof Response)) throw new Error('bridge MCP handler returned non-Response');
54
+ await writeResponse(res,response);
55
+ };
56
+ }
@@ -0,0 +1,14 @@
1
+ import {fromJsonSchema} from '@modelcontextprotocol/server';
2
+
3
+ // Catalog is fixed by the existing downstream startup attestation. Only schema
4
+ // definitions/validators are shared; servers, callbacks and requestState aren't.
5
+ export function preparePublicToolDefinitions(tools){
6
+ return Object.freeze(tools.map(tool=>{
7
+ const input=structuredClone(tool.inputSchema??{type:'object',properties:{},additionalProperties:false});
8
+ if(tool.name==='autonomous_goal_run'){
9
+ if(input.properties&&typeof input.properties==='object')delete input.properties.planner_response;
10
+ if(Array.isArray(input.required))input.required=input.required.filter(name=>name!=='planner_response');
11
+ }
12
+ return Object.freeze({...tool,inputSchema:fromJsonSchema(input),outputSchema:tool.outputSchema?fromJsonSchema(tool.outputSchema):undefined});
13
+ }));
14
+ }