deadbyte-mcp 0.9.0 → 0.11.0

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 (136) hide show
  1. package/MANIFEST.SHA256 +134 -92
  2. package/README.txt +19 -15
  3. package/bench/fixtures/corpus.json +32 -13
  4. package/bench/fixtures/r36/case-map.json +8 -0
  5. package/bin/WORKER-REGISTRY.txt +3 -3
  6. package/bin/appcontainer-stage.exe +0 -0
  7. package/bin/appcontainer-stage.obj +0 -0
  8. package/bin/bootstrap-advapi32.exe +0 -0
  9. package/bin/bootstrap-advapi32.obj +0 -0
  10. package/bin/bootstrap-exitcode.exe +0 -0
  11. package/bin/bootstrap-exitcode.obj +0 -0
  12. package/bin/bootstrap-kernel32.exe +0 -0
  13. package/bin/bootstrap-kernel32.obj +0 -0
  14. package/bin/child-control-probe.exe +0 -0
  15. package/bin/child-control-probe.obj +0 -0
  16. package/bin/child-control-stage.exe +0 -0
  17. package/bin/child-control-stage.obj +0 -0
  18. package/bin/contained-reverse-worker.exe +0 -0
  19. package/bin/contained-reverse-worker.obj +0 -0
  20. package/bin/contained-transform-worker.exe +0 -0
  21. package/bin/contained-transform-worker.obj +0 -0
  22. package/bin/containment-probe.exe +0 -0
  23. package/bin/containment-probe.obj +0 -0
  24. package/bin/deadbyte-contain.exe +0 -0
  25. package/bin/deadbyte-contain.obj +0 -0
  26. package/bin/deadbyte-exec.exe +0 -0
  27. package/bin/deadbyte-exec.obj +0 -0
  28. package/bin/deadbyte-process-host.exe +0 -0
  29. package/bin/deadbyte-process-host.obj +0 -0
  30. package/bin/deadbyte-tunnel-host.exe +0 -0
  31. package/bin/deadbyte-tunnel-host.obj +0 -0
  32. package/controller/README.TXT +8 -6
  33. package/controller/deadbyte-controller.ps1 +48 -2
  34. package/controller/deadbyte-process-policy.json +5 -5
  35. package/docs/ARCHITECTURE.md +1 -1
  36. package/docs/R36-CAPABILITY-EVIDENCE.md +43 -0
  37. package/docs/superpowers/plans/2026-09-18-r36-zero-loss-autonomy.md +739 -0
  38. package/docs/superpowers/specs/2026-09-18-r36-zero-loss-autonomy-design.md +640 -0
  39. package/package.json +1 -1
  40. package/proof/CONTAINMENT-BUILD.txt +6 -6
  41. package/scripts/deferred-slot-operation.mjs +53 -15
  42. package/scripts/final-closure-r36.mjs +113 -0
  43. package/scripts/final-closure-verify-r36.mjs +208 -0
  44. package/scripts/final-closure-verify.mjs +88 -39
  45. package/scripts/final-closure.mjs +63 -56
  46. package/scripts/gate-windows-r36.ps1 +42 -0
  47. package/scripts/gate-windows.ps1 +3 -3
  48. package/scripts/release-parity-r36.mjs +200 -0
  49. package/scripts/release-parity-tests.ps1 +9 -0
  50. package/scripts/release-parity.mjs +149 -139
  51. package/scripts/windows-gate-evidence-r36.mjs +52 -0
  52. package/scripts/windows-gate-evidence.mjs +5 -5
  53. package/src/agent-memory-plane.mjs +98 -0
  54. package/src/autonomous-context-engine.mjs +2 -1
  55. package/src/autonomous-mcp-tools.mjs +5 -1
  56. package/src/autonomous-output-contracts.mjs +9 -6
  57. package/src/autonomous-planner-contracts.mjs +36 -1
  58. package/src/autonomous-planner.mjs +25 -3
  59. package/src/autonomous-runtime.mjs +337 -43
  60. package/src/autonomous-timeout-recovery.mjs +27 -0
  61. package/src/capability-benchmark-runner.mjs +86 -1
  62. package/src/capability-benchmark.mjs +45 -0
  63. package/src/capability-ledger.mjs +158 -0
  64. package/src/coding-mcp-tools.mjs +31 -17
  65. package/src/coding-output-contracts.mjs +48 -10
  66. package/src/coding-plane.mjs +45 -9
  67. package/src/coding-search.mjs +130 -47
  68. package/src/compact-gateway-mcp-tools.mjs +128 -0
  69. package/src/compact-gateway-output-contracts.mjs +16 -0
  70. package/src/deadbyte-cli.mjs +62 -78
  71. package/src/effect-ledger.mjs +47 -0
  72. package/src/machine-fs-smoke-client.mjs +1 -1
  73. package/src/machine-fs.mjs +51 -8
  74. package/src/mcp-server.mjs +32 -9
  75. package/src/native-diff-v2.mjs +337 -0
  76. package/src/native-diff.mjs +50 -2
  77. package/src/observation-lease.mjs +32 -0
  78. package/src/observation-smoke-client.mjs +1 -1
  79. package/src/operator-surface.mjs +7 -1
  80. package/src/process-runtime.mjs +98 -11
  81. package/src/process-smoke-client.mjs +11 -3
  82. package/src/r36-compact-disarmed-probe.mjs +50 -0
  83. package/src/r36-full-surface-probe.mjs +56 -0
  84. package/src/remote-live-log.mjs +135 -0
  85. package/src/remote-mcp-autonomous-smoke-client.mjs +22 -12
  86. package/src/remote-mcp-coding-smoke-client.mjs +1 -1
  87. package/src/remote-mcp-r36-parity-client.mjs +175 -0
  88. package/src/remote-mcp-smoke-client.mjs +112 -56
  89. package/src/runtime-update.mjs +51 -1
  90. package/src/search-cursor.mjs +45 -0
  91. package/src/semantic-code.mjs +72 -5
  92. package/src/semantic-lsp-backend.mjs +39 -3
  93. package/src/version.mjs +1 -1
  94. package/src/worker-scheduler.mjs +53 -0
  95. package/test/autonomous-capability-r36.test.mjs +151 -0
  96. package/test/autonomous-output-contract.test.mjs +1 -1
  97. package/test/autonomous-runtime.test.mjs +2 -2
  98. package/test/capability-benchmark-r36.test.mjs +107 -0
  99. package/test/capability-benchmark-semantic-r36.test.mjs +52 -0
  100. package/test/capability-claims-r36.test.mjs +23 -0
  101. package/test/capability-ledger-r36.test.mjs +102 -0
  102. package/test/coding-catalog-consistency.test.mjs +2 -2
  103. package/test/coding-plane.test.mjs +2 -2
  104. package/test/coding-runtime.test.mjs +16 -0
  105. package/test/coding-search-r36.test.mjs +90 -0
  106. package/test/compact-catalog-r36.test.mjs +37 -0
  107. package/test/compact-gateway-r36.test.mjs +66 -0
  108. package/test/contained.test.mjs +1 -1
  109. package/test/core.test.mjs +1 -1
  110. package/test/deferred-slot-operation.test.mjs +9 -0
  111. package/test/fixtures/lsp-framed-server.mjs +2 -0
  112. package/test/helpers/autonomous-r34-fixture.mjs +5 -3
  113. package/test/multi-file-read.test.mjs +3 -0
  114. package/test/native-diff-topology-r36.test.mjs +105 -0
  115. package/test/package-boundary.test.mjs +6 -0
  116. package/test/process-longrun-r36.test.mjs +82 -0
  117. package/test/process-release-gate.test.mjs +9 -0
  118. package/test/production-docs.test.mjs +5 -5
  119. package/test/prompts-resources.test.mjs +7 -1
  120. package/test/r33-closeout-regression.test.mjs +4 -3
  121. package/test/r33-finalization.test.mjs +14 -9
  122. package/test/r36-finalization.test.mjs +104 -0
  123. package/test/r36-release-identity.test.mjs +45 -0
  124. package/test/r37-agent-fabric.test.mjs +92 -0
  125. package/test/r37-autonomous-timeout-recovery.test.mjs +60 -0
  126. package/test/r37-finalization.test.mjs +74 -0
  127. package/test/release-version.test.mjs +42 -33
  128. package/test/remote-live-log-r36.test.mjs +37 -0
  129. package/test/remote-live-log-r37.test.mjs +47 -0
  130. package/test/remote-live-log-recovery-r36.test.mjs +41 -0
  131. package/test/remote-r24-catalog.test.mjs +13 -0
  132. package/test/remote-session-cli.test.mjs +53 -66
  133. package/test/runtime-self-update.test.mjs +58 -1
  134. package/test/semantic-lsp-backend.test.mjs +9 -0
  135. package/test/semantic-r36-rename.test.mjs +82 -0
  136. package/deadbyte-mcp-0.8.9.tgz +0 -0
@@ -1,5 +1,5 @@
1
1
  import { createHash, randomBytes, sign as cryptoSign, verify as cryptoVerify } from 'node:crypto';
2
- import { appendFile, lstat, mkdir, readFile, realpath } from 'node:fs/promises';
2
+ import { appendFile, lstat, mkdir, readFile, readdir, realpath, writeFile } from 'node:fs/promises';
3
3
  import path from 'node:path';
4
4
  import { spawn } from 'node:child_process';
5
5
  import { canonicalJson } from './canonical-json.mjs';
@@ -13,6 +13,7 @@ const ZERO_SHA256 = '0'.repeat(64);
13
13
  const SHA256_RE = /^[0-9a-f]{64}$/;
14
14
  const READY_PREFIX = Buffer.from('DEADBYTE_PROCESS_HOST_READY ', 'utf8');
15
15
  const START_TIMEOUT_MS = 8000;
16
+ const TERMINAL_RECOVERY_SCHEMA = 'deadbyte.process-terminal.v1';
16
17
 
17
18
  const sha256Bytes = bytes => createHash('sha256').update(bytes).digest('hex');
18
19
  const nowUtc = () => new Date().toISOString();
@@ -101,15 +102,85 @@ export function createProcessRuntime({ policy, signingKeyPath, verifyKeyPath })
101
102
  const sessions = new Map();
102
103
  let signingPromise = null;
103
104
  let verifyPromise = null;
105
+ let hydrationPromise = null;
104
106
  const runtime = {
105
107
  policy,
106
108
  signing: () => (signingPromise ??= loadPrivateSigningKey(signingKeyPath)),
107
109
  verifying: () => (verifyPromise ??= loadPublicVerifyKey(verifyKeyPath))
108
110
  };
109
111
 
110
- function sessionFor(sessionId) {
112
+ async function readVerifiedJournal(sessionId, journalPath) {
113
+ const verifying = await runtime.verifying();
114
+ const text = await readFile(journalPath, 'utf8');
115
+ const records = text.split(/\r?\n/).filter(Boolean).map(line => JSON.parse(line));
116
+ let previous = ZERO_SHA256;
117
+ let sequence = 0;
118
+ for (const record of records) {
119
+ const { event_sha256: eventSha, key_id: keyId, signature, ...unsigned } = record;
120
+ if (unsigned.schema !== EVENT_SCHEMA || unsigned.session_id !== sessionId || unsigned.sequence !== sequence + 1 ||
121
+ unsigned.previous_event_sha256 !== previous || unsigned.policy_sha256 !== policy.policy_sha256) {
122
+ throw new Error('process event journal chain mismatch');
123
+ }
124
+ const canonical = Buffer.from(canonicalJson(unsigned), 'utf8');
125
+ const observedSha = sha256Bytes(canonical);
126
+ if (eventSha !== observedSha || keyId !== verifying.keyId || typeof signature !== 'string' || !cryptoVerify(null,
127
+ Buffer.concat([EVENT_DOMAIN, canonical]), verifying.publicKey, Buffer.from(signature, 'base64'))) {
128
+ throw new Error('process event journal signature mismatch');
129
+ }
130
+ previous = observedSha;
131
+ sequence += 1;
132
+ }
133
+ return { records, event_count: sequence, head_sha256: previous };
134
+ }
135
+
136
+ async function hydrateTerminalSessions() {
137
+ if (hydrationPromise) return hydrationPromise;
138
+ hydrationPromise = (async () => {
139
+ await mkdir(policy.state_dir, { recursive: true });
140
+ const names = (await readdir(policy.state_dir)).filter(name => /^[0-9a-f]{64}\.jsonl$/.test(name)).sort();
141
+ for (const name of names) {
142
+ const sessionId = name.slice(0, 64);
143
+ if (sessions.has(sessionId)) continue;
144
+ let checked;
145
+ try { checked = await readVerifiedJournal(sessionId, path.join(policy.state_dir, name)); }
146
+ catch { continue; }
147
+ const terminal = checked.records.at(-1);
148
+ if (!terminal || !['exited','terminated'].includes(terminal.type) || terminal.detail?.recovery_schema !== TERMINAL_RECOVERY_SCHEMA) continue;
149
+ const started = checked.records.find(record => record.type === 'started');
150
+ if (!started || !Number.isInteger(started.detail?.child_pid) || !/^[0-9]+$/.test(String(started.detail?.creation_filetime ?? ''))) continue;
151
+ const profile = policy.profiles[terminal.profile_id];
152
+ if (!profile || !policy.roots[terminal.root_id]) continue;
153
+ const outputFile = terminal.detail.output_file;
154
+ if (typeof outputFile !== 'string' || outputFile !== `${sessionId}.stream.bin`) continue;
155
+ let output;
156
+ try { output = await readFile(path.join(policy.state_dir, outputFile)); }
157
+ catch { continue; }
158
+ if (!Number.isInteger(terminal.detail.retained_output_bytes) || terminal.detail.retained_output_bytes !== output.length ||
159
+ !SHA256_RE.test(terminal.detail.retained_output_sha256 ?? '') || sha256Bytes(output) !== terminal.detail.retained_output_sha256 ||
160
+ !SHA256_RE.test(terminal.detail.stream_sha256 ?? '') || !Number.isInteger(terminal.detail.stream_head) ||
161
+ !Number.isInteger(terminal.detail.earliest_offset) || terminal.detail.earliest_offset < 0 ||
162
+ terminal.detail.stream_head < terminal.detail.earliest_offset || terminal.detail.stream_head - terminal.detail.earliest_offset !== output.length) continue;
163
+ sessions.set(sessionId, {
164
+ session_id:sessionId, job_id:null, profile_id:terminal.profile_id, root_id:terminal.root_id,
165
+ cwd_relative:typeof started.detail?.cwd === 'string' ? started.detail.cwd : '.',
166
+ child_pid:started.detail.child_pid, creation_filetime:String(started.detail.creation_filetime),
167
+ state:terminal.type, exit_code:Number.isInteger(terminal.detail.exit_code) ? terminal.detail.exit_code : null,
168
+ earliest_offset:terminal.detail.earliest_offset, stream_head:terminal.detail.stream_head,
169
+ stream_sha256:terminal.detail.stream_sha256, output, control_buffer:Buffer.alloc(0),
170
+ journal_head_sha256:checked.head_sha256, journal_path:path.join(policy.state_dir,name), event_count:checked.event_count,
171
+ max_retained_output_bytes:profile.max_retained_output_bytes, stderr:'', terminate_requested:terminal.type==='terminated',
172
+ protocol_error:null, transport:profile.transport, win32_input_mode:false, host:null, close_promise:Promise.resolve(),
173
+ recovered_terminal:true
174
+ });
175
+ }
176
+ })();
177
+ return hydrationPromise;
178
+ }
179
+
180
+ async function sessionFor(sessionId) {
111
181
  if (typeof sessionId !== 'string' || !SHA256_RE.test(sessionId)) throw new Error('invalid process session id');
112
- const session = sessions.get(sessionId);
182
+ let session = sessions.get(sessionId);
183
+ if (!session) { await hydrateTerminalSessions(); session = sessions.get(sessionId); }
113
184
  if (!session) throw new Error(`unknown process session '${sessionId}'`);
114
185
  return session;
115
186
  }
@@ -172,6 +243,7 @@ export function createProcessRuntime({ policy, signingKeyPath, verifyKeyPath })
172
243
  }
173
244
 
174
245
  async function start({ profileId, rootId, cwd = '.', args = [] }) {
246
+ await hydrateTerminalSessions();
175
247
  const grant = await assertProcessArmed(policy, profileId, rootId);
176
248
  const profile = policy.profiles[profileId];
177
249
  if (!profile) throw new Error(`process profile '${profileId}' is not configured`);
@@ -209,8 +281,23 @@ export function createProcessRuntime({ policy, signingKeyPath, verifyKeyPath })
209
281
  parseHostOutput(session, null, true);
210
282
  if (session.state === 'starting') session.ready_reject?.(new Error(`process host exited before READY: ${session.stderr}`));
211
283
  session.exit_code ??= Number.isInteger(code) ? code : null;
212
- session.state = session.terminate_requested ? 'terminated' : 'exited';
213
- try { await appendEvent(runtime, session, session.state, { exit_code: session.exit_code }); } catch {}
284
+ const terminalState = session.terminate_requested ? 'terminated' : 'exited';
285
+ try {
286
+ const outputFile = `${session.session_id}.stream.bin`;
287
+ const retainedOutputSha256 = sha256Bytes(session.output);
288
+ await writeFile(path.join(policy.state_dir, outputFile), session.output, { flag:'wx' });
289
+ await appendEvent(runtime, session, terminalState, {
290
+ recovery_schema:TERMINAL_RECOVERY_SCHEMA,
291
+ exit_code:session.exit_code,
292
+ earliest_offset:session.earliest_offset,
293
+ stream_head:session.stream_head,
294
+ stream_sha256:session.stream_sha256,
295
+ retained_output_bytes:session.output.length,
296
+ retained_output_sha256:retainedOutputSha256,
297
+ output_file:outputFile
298
+ });
299
+ session.state = terminalState;
300
+ } catch {}
214
301
  resolve();
215
302
  });
216
303
  });
@@ -223,7 +310,7 @@ export function createProcessRuntime({ policy, signingKeyPath, verifyKeyPath })
223
310
  }
224
311
 
225
312
  async function read({ sessionId, offset = 0, maxBytes = 65536 }) {
226
- const session = sessionFor(sessionId);
313
+ const session = await sessionFor(sessionId);
227
314
  if (!Number.isInteger(offset) || offset < 0 || !Number.isInteger(maxBytes) || maxBytes < 1 || maxBytes > 262144) throw new Error('invalid process read bounds');
228
315
  if (offset > session.stream_head) throw new Error('process read offset is beyond stream head');
229
316
  const truncated = offset < session.earliest_offset;
@@ -235,10 +322,10 @@ export function createProcessRuntime({ policy, signingKeyPath, verifyKeyPath })
235
322
  truncated, data_base64: data.toString('base64'), state: session.state, exit_code: session.exit_code };
236
323
  }
237
324
 
238
- async function status({ sessionId }) { return publicSession(sessionFor(sessionId)); }
239
- async function list() { return { sessions: [...sessions.values()].map(publicSession).sort((a,b) => a.session_id.localeCompare(b.session_id)) }; }
325
+ async function status({ sessionId }) { return publicSession(await sessionFor(sessionId)); }
326
+ async function list() { await hydrateTerminalSessions(); return { sessions: [...sessions.values()].map(publicSession).sort((a,b) => a.session_id.localeCompare(b.session_id)) }; }
240
327
  async function action({ sessionId, actionId }) {
241
- const session = sessionFor(sessionId);
328
+ const session = await sessionFor(sessionId);
242
329
  if (session.state !== 'running') throw new Error('process action requires a running session');
243
330
  await assertProcessArmed(policy, session.profile_id, session.root_id);
244
331
  const profile = policy.profiles[session.profile_id];
@@ -259,7 +346,7 @@ export function createProcessRuntime({ policy, signingKeyPath, verifyKeyPath })
259
346
  return { action_id: actionId, logical_bytes: logicalBytes.length, logical_sha256: logicalSha256, session: publicSession(session) };
260
347
  }
261
348
  async function terminate({ sessionId }) {
262
- const session = sessionFor(sessionId);
349
+ const session = await sessionFor(sessionId);
263
350
  if (['exited','terminated'].includes(session.state)) return publicSession(session);
264
351
  session.terminate_requested = true;
265
352
  session.host.kill();
@@ -268,7 +355,7 @@ export function createProcessRuntime({ policy, signingKeyPath, verifyKeyPath })
268
355
  }
269
356
 
270
357
  async function verifyEventJournal({ sessionId }) {
271
- const session = sessionFor(sessionId);
358
+ const session = await sessionFor(sessionId);
272
359
  const verifying = await runtime.verifying();
273
360
  const text = await readFile(session.journal_path, 'utf8');
274
361
  const records = text.split(/\r?\n/).filter(Boolean).map(line => JSON.parse(line));
@@ -14,10 +14,18 @@ const ZERO_SHA256 = '0'.repeat(64);
14
14
  const sha256 = bytes => createHash('sha256').update(bytes).digest('hex');
15
15
  const here = path.dirname(fileURLToPath(import.meta.url));
16
16
  const repoRoot = path.resolve(here, '..');
17
- const policyPath = path.join(repoRoot, 'controller', 'deadbyte-process-policy.json');
17
+ const productionPolicyPath = path.join(repoRoot, 'controller', 'deadbyte-process-policy.json');
18
18
  const serverPath = path.join(repoRoot, 'src', 'mcp-server.mjs');
19
- const policy = await loadProcessPolicy(policyPath);
20
19
  const smokeTrust = await import('node:fs/promises').then(({ mkdtemp }) => mkdtemp(path.join(os.tmpdir(), 'deadbyte-process-smoke-')));
20
+ const smokePolicyPath = path.join(smokeTrust, 'smoke-policy.json');
21
+ const productionPolicy = JSON.parse(await readFile(productionPolicyPath, 'utf8'));
22
+ const smokePolicy = {
23
+ ...productionPolicy,
24
+ grant_file: path.join(smokeTrust, 'process-grant.json'),
25
+ state_dir: path.join(smokeTrust, 'process-state')
26
+ };
27
+ await writeFile(smokePolicyPath, `${JSON.stringify(smokePolicy, null, 2)}\n`, { flag: 'wx' });
28
+ const policy = await loadProcessPolicy(smokePolicyPath);
21
29
  const keys = await generateTrustKeypair({ privateKeyPath: path.join(smokeTrust, 'private.pem'), publicKeyPath: path.join(smokeTrust, 'public.pem') });
22
30
  await mkdir(path.dirname(policy.grant_file), { recursive: true });
23
31
  const grant = { schema: PROCESS_GRANT_SCHEMA, policy_sha256: policy.policy_sha256,
@@ -29,7 +37,7 @@ const client = new Client({ name: 'deadbyte-process-smoke', version: '0.8.0' },
29
37
  { versionNegotiation: { mode: { pin: '2026-07-28' } } });
30
38
  const transport = new StdioClientTransport({
31
39
  command: process.execPath, args: [serverPath], cwd: repoRoot,
32
- env: { ...getDefaultEnvironment(), DEADBYTE_PROCESS_RUNTIME: '1', DEADBYTE_PROCESS_POLICY: policyPath,
40
+ env: { ...getDefaultEnvironment(), DEADBYTE_PROCESS_RUNTIME: '1', DEADBYTE_PROCESS_POLICY: smokePolicyPath,
33
41
  DEADBYTE_SIGNING_KEY: keys.privateKeyPath, DEADBYTE_VERIFY_KEY: keys.publicKeyPath },
34
42
  stderr: 'pipe'
35
43
  });
@@ -0,0 +1,50 @@
1
+ import { createHash, randomBytes } from 'node:crypto';
2
+ import path from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+ import { Client, StreamableHTTPClientTransport } from '@modelcontextprotocol/client';
5
+ import { R36_COMPACT_GATEWAYS, R36_COMPACT_TOOL_LIMIT } from './remote-mcp-r36-parity-client.mjs';
6
+
7
+ const sha256=bytes=>createHash('sha256').update(bytes).digest('hex');
8
+ function structured(result,label){
9
+ if(result.isError) throw new Error(`${label} returned error: ${JSON.stringify(result.structuredContent??result.content)}`);
10
+ if(!result.structuredContent||typeof result.structuredContent!=='object') throw new Error(`${label} missing structured output`);
11
+ return result.structuredContent;
12
+ }
13
+ function gateway(result,label){
14
+ const value=structured(result,label);
15
+ if(value.status!=='ok'||!value.result||typeof value.result!=='object') throw new Error(`${label} gateway status=${String(value.status)}`);
16
+ return value.result;
17
+ }
18
+ export async function probeR36CompactDisarmed(endpointText){
19
+ const endpoint=new URL(endpointText);
20
+ if(endpoint.protocol!=='https:') throw new Error('R36 compact disarmed probe requires HTTPS');
21
+ const client=new Client({name:'deadbyte-r36-compact-disarmed-probe',version:'0.10.0'},{versionNegotiation:{mode:{pin:'2026-07-28'}}});
22
+ const transport=new StreamableHTTPClientTransport(endpoint);
23
+ try{
24
+ await client.connect(transport);
25
+ const listed=await client.listTools();
26
+ const names=listed.tools.map(tool=>tool.name).sort();
27
+ if(names.length>R36_COMPACT_TOOL_LIMIT) throw new Error(`R36 compact catalog exceeds ${R36_COMPACT_TOOL_LIMIT}: ${names.length}`);
28
+ for(const name of R36_COMPACT_GATEWAYS) if(!names.includes(name)) throw new Error(`R36 compact gateway missing ${name}`);
29
+ for(const tool of listed.tools) if(tool.outputSchema?.type!=='object'||tool.outputSchema?.additionalProperties!==false||!tool.outputSchema?.properties) throw new Error(`tool ${tool.name} missing strict output schema`);
30
+ const manifest=gateway(await client.callTool({name:'capability_manifest',arguments:{}}),'capability_manifest');
31
+ if(manifest.profile!=='compact'||!/^[0-9a-f]{64}$/.test(String(manifest.ledger_sha256??''))) throw new Error('R36 compact manifest malformed');
32
+ const required=manifest.capabilities.filter(item=>item?.production===true&&item?.host_only!==true);
33
+ for(const item of required) if(typeof item.compact_tool!=='string'||!names.includes(item.compact_tool)) throw new Error(`compact route missing ${String(item.id)}`);
34
+ const coding=structured(await client.callTool({name:'coding_session_status',arguments:{}}),'coding_session_status');
35
+ const autonomous=structured(await client.callTool({name:'autonomous_session_status',arguments:{}}),'autonomous_session_status');
36
+ if(coding.armed!==false||autonomous.coding_armed!==false||autonomous.autonomous_armed!==false) throw new Error('R36 compact authority must be DISARMED');
37
+ const machine=gateway(await client.callTool({name:'machine_observe',arguments:{op:'machine_status'}}),'machine_status');
38
+ const nonce=`r36-final-${randomBytes(16).toString('hex')}`;
39
+ const ping=gateway(await client.callTool({name:'machine_observe',arguments:{op:'machine_ping',nonce}}),'machine_ping');
40
+ if(ping.nonce!==nonce||ping.machine_id!==machine.machine_id||ping.release_id!==machine.release_id||ping.release_manifest_sha256!==machine.release_manifest_sha256) throw new Error('R36 compact machine ping binding mismatch');
41
+ return {status:'passed',profile:'compact',tool_count:names.length,tools:names,catalog_sha256:sha256(Buffer.from(JSON.stringify(names),'utf8')),strict_output_schemas:true,ledger_sha256:manifest.ledger_sha256,capability_count:manifest.capabilities.length,production_non_host_capabilities:required.length,capability_closure:true,coding_armed:false,autonomous_armed:false,release_id:machine.release_id,release_manifest_sha256:machine.release_manifest_sha256,machine_status:machine,machine_ping:ping,nonce,protocol_revision:client.getNegotiatedProtocolVersion()};
42
+ } finally {await client.close().catch(()=>{});}
43
+ }
44
+
45
+ const invoked=process.argv[1]&&path.resolve(process.argv[1])===fileURLToPath(import.meta.url);
46
+ if(invoked){
47
+ const endpoint=process.argv[2];
48
+ if(!endpoint) throw new Error('usage: node src/r36-compact-disarmed-probe.mjs https://host/mcp/path');
49
+ probeR36CompactDisarmed(endpoint).then(v=>console.log(JSON.stringify(v,null,2))).catch(e=>{console.error(e instanceof Error?e.stack:String(e));process.exitCode=1;});
50
+ }
@@ -0,0 +1,56 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { readFile } from 'node:fs/promises';
3
+ import os from 'node:os';
4
+ import path from 'node:path';
5
+ import { Client } from '@modelcontextprotocol/client';
6
+ import { StdioClientTransport, getDefaultEnvironment } from '@modelcontextprotocol/client/stdio';
7
+ import { fileURLToPath } from 'node:url';
8
+ import { R36_EXPECTED_TOOLS } from './remote-mcp-r36-parity-client.mjs';
9
+
10
+ const sha256=bytes=>createHash('sha256').update(bytes).digest('hex');
11
+
12
+ export async function probeR36FullSurface({packageRoot,generationRoot,stateRoot=path.join(os.homedir(),'.deadbyte-mcp')}={}){
13
+ const pkgRoot=packageRoot?path.resolve(packageRoot):path.resolve(path.dirname(fileURLToPath(import.meta.url)),'..');
14
+ const gen=path.resolve(generationRoot);
15
+ const semanticPath=path.join(gen,'semantic-lsp-config.json');
16
+ const semanticSha=sha256(await readFile(semanticPath));
17
+ const env={
18
+ ...getDefaultEnvironment(),
19
+ DEADBYTE_STATE_ROOT:path.resolve(stateRoot),
20
+ DEADBYTE_CORE_ROOT:pkgRoot,
21
+ DEADBYTE_WORKSPACE_ROOT:path.join(pkgRoot,'workspaces'),
22
+ DEADBYTE_CAPABILITY_PROFILE:'full',
23
+ DEADBYTE_AUDIT_RUNTIME:'0',
24
+ DEADBYTE_MACHINE_RUNTIME:'1',
25
+ DEADBYTE_AGENT_RUNTIME:'1',
26
+ DEADBYTE_PROCESS_RUNTIME:'1',
27
+ DEADBYTE_PROCESS_POLICY:path.join(gen,'process-policy.json'),
28
+ DEADBYTE_CODING_RUNTIME:'1',
29
+ DEADBYTE_MACHINE_FS_RUNTIME:'1',
30
+ DEADBYTE_OBSERVATION_RUNTIME:'1',
31
+ DEADBYTE_CODING_POLICY:path.join(gen,'coding-policy.json'),
32
+ DEADBYTE_SEMANTIC_LSP_CONFIG:semanticPath,
33
+ DEADBYTE_SEMANTIC_LSP_CONFIG_SHA256:semanticSha,
34
+ DEADBYTE_AUTONOMOUS_RUNTIME:'1',
35
+ DEADBYTE_AUTONOMOUS_POLICY:path.join(gen,'autonomous-policy.json'),
36
+ DEADBYTE_PRIVILEGED_POLICY:path.join(gen,'host-policy.json')
37
+ };
38
+ const client=new Client({name:'deadbyte-r36-full-surface-probe',version:'0.10.0'},{versionNegotiation:{mode:{pin:'2026-07-28'}}});
39
+ const transport=new StdioClientTransport({command:process.execPath,args:[path.join(pkgRoot,'src','mcp-server.mjs')],cwd:pkgRoot,env,stderr:'pipe'});
40
+ try{
41
+ await client.connect(transport);
42
+ const listed=await client.listTools();
43
+ const names=listed.tools.map(tool=>tool.name).sort();
44
+ if(JSON.stringify(names)!==JSON.stringify(R36_EXPECTED_TOOLS)) throw new Error(`R36 full catalog mismatch count=${names.length}`);
45
+ for(const tool of listed.tools){
46
+ if(tool.outputSchema?.type!=='object'||tool.outputSchema?.additionalProperties!==false||!tool.outputSchema?.properties) throw new Error(`R36 full tool ${tool.name} missing strict output schema`);
47
+ }
48
+ return {status:'passed',profile:'full',tool_count:names.length,tools:names,catalog_sha256:sha256(Buffer.from(JSON.stringify(names),'utf8')),strict_output_schemas:true,protocol_revision:client.getNegotiatedProtocolVersion()};
49
+ } finally {await client.close().catch(()=>{});}
50
+ }
51
+
52
+ if(process.argv[1]&&path.resolve(process.argv[1])===fileURLToPath(import.meta.url)){
53
+ const [packageRoot,generationRoot]=process.argv.slice(2);
54
+ if(!packageRoot||!generationRoot) throw new Error('usage: node src/r36-full-surface-probe.mjs <package-root> <generation-root>');
55
+ probeR36FullSurface({packageRoot,generationRoot}).then(v=>console.log(JSON.stringify(v,null,2))).catch(e=>{console.error(e instanceof Error?e.stack:String(e));process.exitCode=1;});
56
+ }
@@ -0,0 +1,135 @@
1
+ const SAFE_ARG_KEYS=Object.freeze(['root_id','path','pattern','command_id','profile_id','session_id','goal_id','task_id','step_id','mode']);
2
+ const LIFECYCLE=Object.freeze({
3
+ device_start:'🚀 [DEVICE] Starting DEADBYTE MCP...',
4
+ bridge_connected:'🔌 [BRIDGE] Local bridge connected',
5
+ cloud_connected:'🔌 [CLOUD] Remote MCP connected',
6
+ session_new:'[SESSION] New foreground session',
7
+ session_restored:'♻️ [SESSION] Previous result stream replayed',
8
+ ready:'✅ [READY] DEADBYTE MCP online',
9
+ cleanup:'🔐 [AUTH] Revoking session authority...'
10
+ });
11
+
12
+ function safeAtom(value){
13
+ if(typeof value==='string'){
14
+ if(value.length<1||value.length>160||/[\r\n\0]/.test(value)) return null;
15
+ return value;
16
+ }
17
+ if(typeof value==='number'&&Number.isFinite(value)) return String(value);
18
+ if(typeof value==='boolean') return String(value);
19
+ return null;
20
+ }
21
+
22
+ function summarizeArgs(args){
23
+ if(!args||typeof args!=='object'||Array.isArray(args)) return '';
24
+ const parts=[];
25
+ for(const key of SAFE_ARG_KEYS){
26
+ if(!Object.hasOwn(args,key)) continue;
27
+ const value=safeAtom(args[key]);
28
+ if(value!==null) parts.push(`${key.replace(/_id$/,'')}=${value}`);
29
+ }
30
+ return parts.length?` ${parts.join(' ')}`:'';
31
+ }
32
+
33
+ function formatDuration(ms){
34
+ const value=Number(ms);
35
+ if(!Number.isFinite(value)||value<0) return '';
36
+ if(value<1000) return `${Math.round(value)}ms`;
37
+ return `${(value/1000).toFixed(2)}s`;
38
+ }
39
+
40
+ export function formatCompactAuditEvent(event){
41
+ if(!event||typeof event!=='object'||Array.isArray(event)||typeof event.tool!=='string'||!event.tool) return null;
42
+ if(event.event_type==='tool.request') return `▶ RUN ${event.tool}${summarizeArgs(event.args)}`;
43
+ if(event.event_type==='tool.result'){
44
+ const marker=event.outcome?.is_error===true?'❌ ERR':'✅ OK';
45
+ const duration=formatDuration(event.outcome?.duration_ms);
46
+ return `${marker} ${event.tool}${duration?` ${duration}`:''}`;
47
+ }
48
+ return null;
49
+ }
50
+
51
+ export function formatRemoteLifecycle(kind){
52
+ if(typeof kind!=='string'||!Object.hasOwn(LIFECYCLE,kind)) throw new Error(`unknown remote lifecycle '${String(kind)}'`);
53
+ return LIFECYCLE[kind];
54
+ }
55
+
56
+ export function createLiveActivityTracker(){
57
+ const pending=new Map();
58
+ return Object.freeze({
59
+ observe(event){
60
+ if(!event||typeof event!=='object'||Array.isArray(event)) return null;
61
+ if(event.event_type==='tool.request'&&typeof event.request_id==='string'&&event.request_id&&typeof event.tool==='string'&&event.tool){
62
+ pending.set(event.request_id,{request_id:event.request_id,tool:event.tool});
63
+ }else if(event.event_type==='tool.result'&&typeof event.request_id==='string'&&event.request_id){
64
+ pending.delete(event.request_id);
65
+ }
66
+ return formatCompactAuditEvent(event);
67
+ },
68
+ pending(){return [...pending.values()].map(item=>({...item}));},
69
+ reconcileInterrupted(reason='session_end'){
70
+ const suffix=reason==='session_end'?'session ended before result':reason==='recovery'?'session recovered before result':'result not observed before stream ended';
71
+ const lines=[...pending.values()].map(item=>`⚠️ INTERRUPTED ${item.tool} — ${suffix}`);
72
+ pending.clear();
73
+ return lines;
74
+ }
75
+ });
76
+ }
77
+
78
+
79
+ function shortSpanId(requestId){
80
+ return typeof requestId==='string'&&requestId.length?requestId.slice(0,8):'????????';
81
+ }
82
+
83
+ export function createLiveTimelineTracker({clock=()=>Date.now()}={}){
84
+ if(typeof clock!=='function') throw new Error('live timeline clock must be a function');
85
+ const pending=new Map();
86
+ const completed=new Set();
87
+ return Object.freeze({
88
+ observe(event){
89
+ if(!event||typeof event!=='object'||Array.isArray(event)) return null;
90
+ const requestId=typeof event.request_id==='string'&&event.request_id?event.request_id:null;
91
+ const tool=typeof event.tool==='string'&&event.tool?event.tool:null;
92
+ if(event.event_type==='tool.request'&&requestId&&tool){
93
+ if(pending.has(requestId)||completed.has(requestId)) return null;
94
+ pending.set(requestId,{request_id:requestId,tool,started_at_ms:clock()});
95
+ return `▶ [${shortSpanId(requestId)}] RUN ${tool}${summarizeArgs(event.args)}`;
96
+ }
97
+ if(event.event_type==='tool.result'&&requestId&&tool){
98
+ if(completed.has(requestId)) return null;
99
+ const span=pending.get(requestId)??null;
100
+ pending.delete(requestId);completed.add(requestId);
101
+ const error=event.outcome?.is_error===true;
102
+ const duration=Number.isFinite(Number(event.outcome?.duration_ms))
103
+ ? formatDuration(event.outcome.duration_ms)
104
+ : span?formatDuration(Math.max(0,clock()-span.started_at_ms)):'';
105
+ const marker=span?(error?'✗':'✓'):'↩';
106
+ const status=error?'ERR':'OK ';
107
+ const suffix=span?'':' (request before live window)';
108
+ return `${marker} [${shortSpanId(requestId)}] ${status} ${tool}${duration?` ${duration}`:''}${suffix}`;
109
+ }
110
+ return null;
111
+ },
112
+ pending(){return [...pending.values()].map(({request_id,tool})=>({request_id,tool}));},
113
+ reconcileInterrupted(reason='session_end'){
114
+ const suffix=reason==='recovery'?'recovery ended before terminal result':
115
+ reason==='session_end'?'session ended before terminal result':'stream ended before terminal result';
116
+ const lines=[...pending.values()].map(item=>`⚠ [${shortSpanId(item.request_id)}] INTERRUPTED ${item.tool} — ${suffix}`);
117
+ pending.clear();
118
+ return lines;
119
+ }
120
+ });
121
+ }
122
+
123
+
124
+ export function formatRuntimeDiagnosticLine(file,line){
125
+ if(typeof file!=='string'||typeof line!=='string'||!line) return null;
126
+ const label=file.split(/[\\/]/).at(-1).replace(/\.log$/i,'').toUpperCase();
127
+ const input=line.match(/\[TOOL IN \]\s+\S+\s+#\d+\s+([A-Za-z0-9_.-]+)/);
128
+ if(input) return `[${label}] IN ${input[1]}`;
129
+ const output=line.match(/\[TOOL OUT\]\s+\S+\s+#\d+\s+([A-Za-z0-9_.-]+)/);
130
+ if(output){
131
+ if(/"is_error"\s*:\s*true/.test(line)) return `[${label}] ERR ${output[1]}`;
132
+ return `[${label}] OUT ${output[1]}`;
133
+ }
134
+ return null;
135
+ }
@@ -3,8 +3,9 @@ import { readFile } from 'node:fs/promises';
3
3
  import path from 'node:path';
4
4
  import { fileURLToPath } from 'node:url';
5
5
  import { Client, StreamableHTTPClientTransport } from '@modelcontextprotocol/client';
6
- import { R34_EXPECTED_TOOLS } from './remote-mcp-r34-parity-client.mjs';
6
+ import { R36_COMPACT_GATEWAYS, R36_COMPACT_TOOL_LIMIT } from './remote-mcp-r36-parity-client.mjs';
7
7
  import { promptContexts, promptObservations, promptMutationEpoch } from './autonomous-live-state.mjs';
8
+ import { classifyAutonomousTimeoutRecovery } from './autonomous-timeout-recovery.mjs';
8
9
 
9
10
  const endpointText = process.argv[2];
10
11
  if (!endpointText) throw new Error('usage: node src/remote-mcp-autonomous-smoke-client.mjs https://host/mcp/path');
@@ -26,22 +27,21 @@ function structured(result, label) {
26
27
  if (!result.structuredContent || typeof result.structuredContent !== 'object') throw new Error(`${label} returned no structured object`);
27
28
  return result.structuredContent;
28
29
  }
29
- async function recoverTimedOutGoal(client, goalId) {
30
+ async function recoverTimedOutGoal(client, goalId, beforeJournalHead) {
30
31
  for (let poll = 0; poll < AUTONOMOUS_TIMEOUT_RECOVERY_MAX_POLLS; poll += 1) {
31
32
  const status = structured(await client.callTool(
32
33
  { name:'autonomous_goal_status', arguments:{ goal_id:goalId } },
33
34
  { timeout:30000, maxTotalTimeout:30000 }
34
35
  ), 'autonomous_goal_status');
35
- if (status.state === 'succeeded') return status;
36
- if (status.state === 'cancelled' || status.state === 'paused') {
36
+ const disposition=classifyAutonomousTimeoutRecovery(status,{beforeJournalHead});
37
+ if (disposition.kind === 'completed') return { kind:'completed', status };
38
+ if (disposition.kind === 'terminal_failure') {
37
39
  throw new Error(`autonomous goal became terminal after request timeout: ${JSON.stringify(status)}`);
38
40
  }
39
- if (status.state !== 'pending' && status.state !== 'running') {
40
- throw new Error(`autonomous goal returned unexpected recovery state: ${JSON.stringify(status)}`);
41
- }
41
+ if (disposition.kind === 'resumable') return { kind:'resumable', status };
42
42
  await sleep(AUTONOMOUS_TIMEOUT_RECOVERY_POLL_MS);
43
43
  }
44
- throw new Error('autonomous goal did not reach a durable terminal state after request timeout');
44
+ throw new Error('autonomous goal did not reach a durable terminal or resumable boundary after request timeout');
45
45
  }
46
46
  function messageText(content) {
47
47
  if (typeof content === 'string') return content;
@@ -96,7 +96,7 @@ function plannerDecision(prompt) {
96
96
  return { schema:'deadbyte.autonomous-decision.v1', action:'inspect', summary:'gather current fixture state before deciding next action',
97
97
  reads:[{ path:fixtureRelative, max_bytes:4096 }] };
98
98
  }const client = new Client(
99
- { name:'deadbyte-r24-public-autonomous-gate', version:'0.8.0' },
99
+ { name:'deadbyte-r36-public-autonomous-gate', version:'0.10.0' },
100
100
  { capabilities:{ sampling:{} }, versionNegotiation:{ mode:{ pin:'2026-07-28' } },
101
101
  inputRequired:{ autoFulfill:true, maxRounds:16 } }
102
102
  );
@@ -113,7 +113,11 @@ try {
113
113
  await client.connect(transport);
114
114
  const listed = await client.listTools();
115
115
  const names = listed.tools.map(tool => tool.name).sort();
116
- if (JSON.stringify(names) !== JSON.stringify(R34_EXPECTED_TOOLS)) throw new Error(`R34 public catalog mismatch: ${JSON.stringify(names)}`);
116
+ if (names.length > R36_COMPACT_TOOL_LIMIT) throw new Error(`R36 compact public catalog exceeds ${R36_COMPACT_TOOL_LIMIT}: ${names.length}`);
117
+ for (const name of R36_COMPACT_GATEWAYS) if (!names.includes(name)) throw new Error(`R36 compact public gateway missing ${name}`);
118
+ for (const name of ['autonomous_goal_submit','autonomous_goal_run','autonomous_goal_status','autonomous_goal_events','autonomous_goal_verify','autonomous_release_gate','autonomous_session_status','native_diff_apply','native_diff_preview','native_diff_verify']) {
119
+ if (!names.includes(name)) throw new Error(`R36 compact autonomous control missing ${name}`);
120
+ }
117
121
  for (const tool of listed.tools) {
118
122
  if (tool.outputSchema?.type !== 'object' || tool.outputSchema?.additionalProperties !== false || !tool.outputSchema?.properties) {
119
123
  throw new Error(`tool ${tool.name} missing strict output schema`);
@@ -133,6 +137,10 @@ try {
133
137
  let ran = null;
134
138
  let recoveredRequestTimeout = false;
135
139
  for (let requestRound = 0; requestRound < AUTONOMOUS_RUN_MAX_REQUESTS; requestRound += 1) {
140
+ const beforeRun=structured(await client.callTool(
141
+ { name:'autonomous_goal_status', arguments:{ goal_id:goalId } },
142
+ { timeout:30000, maxTotalTimeout:30000 }
143
+ ), 'autonomous_goal_status');
136
144
  try {
137
145
  ran = structured(await client.callTool(
138
146
  { name:'autonomous_goal_run', arguments:{ goal_id:goalId, max_cycles:AUTONOMOUS_RUN_MAX_CYCLES_PER_REQUEST } },
@@ -140,8 +148,10 @@ try {
140
148
  ), 'autonomous_goal_run');
141
149
  } catch (error) {
142
150
  if (String(error?.code ?? '') !== 'REQUEST_TIMEOUT') throw error;
143
- const recovered = await recoverTimedOutGoal(client, goalId);
144
- ran = { status:'completed', state:recovered.state, goal_id:goalId, recovered_from_timeout:true };
151
+ const recovered = await recoverTimedOutGoal(client, goalId, beforeRun.journal_head_sha256);
152
+ ran = recovered.kind === 'completed'
153
+ ? { status:'completed', state:recovered.status.state, goal_id:goalId, recovered_from_timeout:true }
154
+ : { status:'yielded', state:recovered.status.state, goal_id:goalId, recovered_from_timeout:true };
145
155
  recoveredRequestTimeout = true;
146
156
  }
147
157
  if (ran.status === 'completed' && ran.state === 'succeeded') break;
@@ -19,7 +19,7 @@ const taskPath = `workspaces/demo/.deadbyte-coding-task-${tag}.txt`;
19
19
  const codingTools = [
20
20
  'coding_benchmark_run', 'coding_command_run', 'coding_file_read', 'coding_file_write', 'coding_files_read',
21
21
  'coding_receipt_verify', 'coding_search', 'coding_session_status', 'coding_stat',
22
- 'coding_symbol_definition', 'coding_symbol_outline', 'coding_symbol_references', 'coding_symbol_replace',
22
+ 'coding_symbol_definition', 'coding_symbol_diagnostics', 'coding_symbol_implementations', 'coding_symbol_outline', 'coding_symbol_references', 'coding_symbol_rename_preview', 'coding_symbol_replace',
23
23
  'coding_task_approve', 'coding_task_cancel', 'coding_task_events', 'coding_task_run', 'coding_task_status',
24
24
  'coding_task_submit', 'coding_task_verify', 'coding_text_replace', 'coding_tree'
25
25
  ];