deadbyte-mcp 0.11.3 → 0.13.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 (127) hide show
  1. package/MANIFEST.SHA256 +126 -54
  2. package/R41-PARENT.json +15 -0
  3. package/README.txt +5 -4
  4. package/acceptance/r40/desktop-live-drift.mjs +106 -0
  5. package/acceptance/r40/desktop-live-ground.mjs +110 -0
  6. package/acceptance/r40/desktop-live-input.mjs +128 -0
  7. package/acceptance/r40/desktop-live-observe.mjs +100 -0
  8. package/acceptance/r40/desktop-live-uia.mjs +121 -0
  9. package/bin/appcontainer-stage.obj +0 -0
  10. package/bin/bootstrap-advapi32.obj +0 -0
  11. package/bin/bootstrap-exitcode.obj +0 -0
  12. package/bin/bootstrap-kernel32.obj +0 -0
  13. package/bin/child-control-probe.obj +0 -0
  14. package/bin/child-control-stage.obj +0 -0
  15. package/bin/contained-reverse-worker.obj +0 -0
  16. package/bin/contained-transform-worker.obj +0 -0
  17. package/bin/containment-probe.obj +0 -0
  18. package/bin/deadbyte-contain.obj +0 -0
  19. package/bin/deadbyte-desktop-input-r40.exe +0 -0
  20. package/bin/deadbyte-desktop-input-r40.obj +0 -0
  21. package/bin/deadbyte-desktop-observer-r40.exe +0 -0
  22. package/bin/deadbyte-desktop-observer-r40.obj +0 -0
  23. package/bin/deadbyte-desktop-uia-r40.exe +0 -0
  24. package/bin/deadbyte-desktop-uia-r40.obj +0 -0
  25. package/bin/deadbyte-exec.obj +0 -0
  26. package/bin/deadbyte-process-host.obj +0 -0
  27. package/bin/deadbyte-tunnel-host.obj +0 -0
  28. package/controller/README.TXT +7 -5
  29. package/controller/deadbyte-controller.config.psd1.example +3 -0
  30. package/controller/deadbyte-controller.ps1 +149 -4
  31. package/controller/deadbyte-desktop-policy.json +41 -0
  32. package/controller/deadbyte-process-policy.json +4 -4
  33. package/docs/ARCHITECTURE.md +6 -1
  34. package/docs/superpowers/specs/2026-09-19-r40-desktop-computer-use-design.md +214 -0
  35. package/docs/superpowers/specs/2026-09-20-r41-deterministic-memory-design.md +132 -0
  36. package/native/deadbyte-desktop-input.cpp +455 -0
  37. package/native/deadbyte-desktop-observer.cpp +253 -0
  38. package/native/deadbyte-desktop-uia.cpp +307 -0
  39. package/package.json +12 -8
  40. package/proof/CONTAINMENT-BUILD.txt +6 -6
  41. package/scripts/build-containment.ps1 +24 -14
  42. package/scripts/build-desktop-r40.ps1 +112 -0
  43. package/scripts/deferred-slot-operation-r40.mjs +126 -0
  44. package/scripts/deferred-slot-operation-r41.mjs +127 -0
  45. package/scripts/final-closure-r40.mjs +114 -0
  46. package/scripts/final-closure-r41.mjs +116 -0
  47. package/scripts/final-closure-verify-r40.mjs +212 -0
  48. package/scripts/final-closure-verify-r41.mjs +215 -0
  49. package/scripts/gate-windows-r40.ps1 +43 -0
  50. package/scripts/gate-windows-r41.ps1 +48 -0
  51. package/scripts/init-desktop-policy-r40.ps1 +85 -0
  52. package/scripts/r40-compact-ledger-probe.mjs +4 -0
  53. package/scripts/release-parity-r40.mjs +212 -0
  54. package/scripts/release-parity-r41.mjs +221 -0
  55. package/scripts/release-parity-tests-r40.ps1 +9 -0
  56. package/scripts/release-parity-tests-r41.ps1 +9 -0
  57. package/scripts/verify-memory-r41.mjs +88 -0
  58. package/scripts/verify-r41-parent.mjs +61 -0
  59. package/scripts/windows-gate-evidence-r40.mjs +52 -0
  60. package/scripts/windows-gate-evidence-r41.mjs +53 -0
  61. package/src/autonomous-context-engine.mjs +3 -1
  62. package/src/autonomous-planner-contracts.mjs +19 -1
  63. package/src/autonomous-planner.mjs +24 -0
  64. package/src/autonomous-runtime.mjs +164 -25
  65. package/src/capability-ledger.mjs +6 -1
  66. package/src/compact-gateway-mcp-tools.mjs +14 -2
  67. package/src/deadbyte-cli.mjs +72 -44
  68. package/src/desktop-contract.mjs +263 -0
  69. package/src/desktop-input-r40.mjs +148 -0
  70. package/src/desktop-mcp-tools-r40.mjs +126 -0
  71. package/src/desktop-memory-r40.mjs +272 -0
  72. package/src/desktop-observer-r40.mjs +231 -0
  73. package/src/desktop-policy-r40.mjs +110 -0
  74. package/src/desktop-runtime-r40.mjs +318 -0
  75. package/src/desktop-uia-r40.mjs +123 -0
  76. package/src/mcp-server.mjs +87 -19
  77. package/src/memory-r41.mjs +692 -0
  78. package/src/r40-compact-disarmed-probe.mjs +71 -0
  79. package/src/r40-full-surface-probe.mjs +34 -0
  80. package/src/release-generation.mjs +38 -5
  81. package/src/remote-console.mjs +41 -0
  82. package/src/remote-live-log.mjs +208 -45
  83. package/src/remote-mcp-desktop-r40-smoke-client.mjs +70 -0
  84. package/src/remote-mcp-r40-parity-client.mjs +200 -0
  85. package/src/remote-mcp-smoke-client.mjs +20 -6
  86. package/src/remote-result-journal.mjs +10 -2
  87. package/src/runtime-update.mjs +55 -17
  88. package/src/tool-audit.mjs +29 -2
  89. package/src/version.mjs +1 -1
  90. package/test/autonomous-capability-r36.test.mjs +43 -1
  91. package/test/autonomous-desktop-r40.test.mjs +188 -0
  92. package/test/capability-ledger-r36.test.mjs +1 -0
  93. package/test/cli-entrypoint.test.mjs +1 -1
  94. package/test/compact-catalog-r36.test.mjs +2 -2
  95. package/test/compact-gateway-r36.test.mjs +56 -4
  96. package/test/compact-max-catalog-r40.test.mjs +95 -0
  97. package/test/controller-desktop-r40.test.mjs +86 -0
  98. package/test/core.test.mjs +1 -1
  99. package/test/desktop-adversarial-r40.test.mjs +62 -0
  100. package/test/desktop-contract-r40.test.mjs +200 -0
  101. package/test/desktop-input-r40.test.mjs +63 -0
  102. package/test/desktop-mcp-tools-r40.test.mjs +61 -0
  103. package/test/desktop-memory-r40.test.mjs +141 -0
  104. package/test/desktop-observer-r40.test.mjs +119 -0
  105. package/test/desktop-policy-r40.test.mjs +111 -0
  106. package/test/desktop-runtime-r40.test.mjs +278 -0
  107. package/test/desktop-uia-r40.test.mjs +42 -0
  108. package/test/full-max-catalog-r40.test.mjs +91 -0
  109. package/test/helpers/autonomous-r34-fixture.mjs +3 -3
  110. package/test/memory-autonomous-integration-r41.test.mjs +31 -0
  111. package/test/memory-history-r41.test.mjs +126 -0
  112. package/test/memory-projections-r41.test.mjs +92 -0
  113. package/test/memory-retrieval-r41.test.mjs +124 -0
  114. package/test/memory-snapshot-r41.test.mjs +106 -0
  115. package/test/r33-closeout-regression.test.mjs +7 -7
  116. package/test/r33-finalization.test.mjs +15 -15
  117. package/test/r37-remote-tool-call-timeline.test.mjs +6 -5
  118. package/test/r40-finalization.test.mjs +118 -0
  119. package/test/r41-finalization.test.mjs +38 -0
  120. package/test/release-generation.test.mjs +41 -9
  121. package/test/release-version.test.mjs +20 -61
  122. package/test/remote-console-r40.test.mjs +38 -0
  123. package/test/remote-live-log-r37.test.mjs +89 -32
  124. package/test/remote-r24-catalog.test.mjs +12 -3
  125. package/test/remote-result-journal.test.mjs +15 -0
  126. package/test/remote-session-cli.test.mjs +69 -46
  127. package/test/runtime-self-update.test.mjs +44 -0
@@ -0,0 +1,71 @@
1
+ import { createHash, randomBytes } from 'node:crypto';
2
+ import { readFile } from 'node:fs/promises';
3
+ import os from 'node:os';
4
+ import path from 'node:path';
5
+ import { fileURLToPath } from 'node:url';
6
+ import { Client, StreamableHTTPClientTransport } from '@modelcontextprotocol/client';
7
+ import { R40_COMPACT_GATEWAYS,R40_COMPACT_TOOL_LIMIT } from './remote-mcp-r40-parity-client.mjs';
8
+ import { loadDesktopPolicy } from './desktop-policy-r40.mjs';
9
+ import { verifyDesktopReceiptObject } from './desktop-runtime-r40.mjs';
10
+ import { loadPublicVerifyKey } from './trust.mjs';
11
+
12
+ const sha256=bytes=>createHash('sha256').update(bytes).digest('hex');
13
+ const stateRoot=path.resolve(process.env.DEADBYTE_STATE_ROOT||path.join(os.homedir(),'.deadbyte-mcp'));
14
+ const verifyKeyPath=path.join(stateRoot,'trust','ed25519-public.pem');
15
+ function structured(result,label){
16
+ if(result.isError) throw new Error(label+' returned error: '+JSON.stringify(result.structuredContent??result.content));
17
+ if(!result.structuredContent||typeof result.structuredContent!=='object') throw new Error(label+' missing structured output');
18
+ return result.structuredContent;
19
+ }
20
+ function gateway(result,label){
21
+ const value=structured(result,label);
22
+ if(value.status!=='ok'||!value.result||typeof value.result!=='object') throw new Error(label+' gateway status='+String(value.status));
23
+ return value.result;
24
+ }
25
+ async function verifyDesktop(policy,verifyKey,id,sha,kind){
26
+ const receipt=JSON.parse(await readFile(path.join(policy.evidence_dir,id+'.json'),'utf8'));
27
+ const proof=verifyDesktopReceiptObject(receipt,{policySha256:policy.policy_sha256,publicKey:verifyKey.publicKey,keyId:verifyKey.keyId});
28
+ if(!proof.ok||proof.receipt_sha256!==sha||proof.kind!==kind) throw new Error('R40 desktop '+kind+' receipt verification failed');
29
+ }
30
+ export async function probeR40CompactDisarmed(endpointText){
31
+ const endpoint=new URL(endpointText);if(endpoint.protocol!=='https:')throw new Error('R40 compact disarmed probe requires HTTPS');
32
+ const pointer=JSON.parse(await readFile(path.join(stateRoot,'active-release.json'),'utf8'));
33
+ const desktopPolicy=await loadDesktopPolicy(path.join(pointer.generation_root,'desktop-policy.json'));
34
+ const verifyKey=await loadPublicVerifyKey(verifyKeyPath);
35
+ const client=new Client({name:'deadbyte-r40-compact-disarmed-probe',version:'0.12.0'},{versionNegotiation:{mode:{pin:'2026-07-28'}}});
36
+ const transport=new StreamableHTTPClientTransport(endpoint);
37
+ try{
38
+ await client.connect(transport);
39
+ const listed=await client.listTools(),names=listed.tools.map(t=>t.name).sort();
40
+ if(names.length!==R40_COMPACT_TOOL_LIMIT)throw new Error('R40 compact disarmed catalog must be exactly '+R40_COMPACT_TOOL_LIMIT+', got '+names.length);
41
+ for(const name of R40_COMPACT_GATEWAYS)if(!names.includes(name))throw new Error('R40 compact gateway missing '+name);
42
+ 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');
43
+ const manifest=gateway(await client.callTool({name:'capability_manifest',arguments:{}}),'capability_manifest');
44
+ if(manifest.schema!=='deadbyte.capability-manifest.v2'||manifest.profile!=='compact'||manifest.tool_count!==names.length||
45
+ JSON.stringify(manifest.tool_catalog)!==JSON.stringify(names))throw new Error('R40 compact manifest malformed');
46
+ const required=manifest.capabilities.filter(x=>x?.production===true&&x?.host_only!==true);
47
+ 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));
48
+ const coding=structured(await client.callTool({name:'coding_session_status',arguments:{}}),'coding status');
49
+ const autonomous=structured(await client.callTool({name:'autonomous_session_status',arguments:{}}),'autonomous status');
50
+ const desktop=gateway(await client.callTool({name:'desktop_control',arguments:{op:'status'}}),'desktop status');
51
+ if(coding.armed!==false||autonomous.coding_armed!==false||autonomous.autonomous_armed!==false||desktop.armed!==false)throw new Error('R40 closure requires Coding/Autonomous/Desktop DISARMED');
52
+ const observed=gateway(await client.callTool({name:'desktop_control',arguments:{op:'observe',preview:false}}),'desktop observe');
53
+ await verifyDesktop(desktopPolicy,verifyKey,observed.receipt_id,observed.receipt_sha256,'desktop_observation');
54
+ const grounded=gateway(await client.callTool({name:'desktop_control',arguments:{op:'target',frame_id:observed.frame.frame_id,uia_query:{kind:'root',value:'',max_results:1,ordinal:0}}}),'desktop target');
55
+ await verifyDesktop(desktopPolicy,verifyKey,grounded.grounding.receipt_id,grounded.grounding.receipt_sha256,'desktop_grounding');
56
+ const machine=gateway(await client.callTool({name:'machine_observe',arguments:{op:'machine_status'}}),'machine status');
57
+ const nonce='r40-final-'+randomBytes(16).toString('hex');
58
+ const ping=gateway(await client.callTool({name:'machine_observe',arguments:{op:'machine_ping',nonce}}),'machine ping');
59
+ if(ping.nonce!==nonce||ping.release_id!==machine.release_id||ping.release_manifest_sha256!==machine.release_manifest_sha256)throw new Error('R40 machine ping binding mismatch');
60
+ return {status:'passed',profile:'compact',tool_count:names.length,tools:names,catalog_sha256:sha256(Buffer.from(JSON.stringify(names),'utf8')),
61
+ strict_output_schemas:true,ledger_sha256:manifest.ledger_sha256,capability_count:manifest.capabilities.length,
62
+ production_non_host_capabilities:required.length,capability_closure:true,coding_armed:false,autonomous_armed:false,desktop_armed:false,
63
+ desktop_observation_receipt_verified:true,desktop_grounding_receipt_verified:true,
64
+ desktop_frame_id:observed.frame.frame_id,desktop_target_id:grounded.target.target_id,
65
+ release_id:machine.release_id,release_manifest_sha256:machine.release_manifest_sha256,machine_status:machine,machine_ping:ping,nonce,
66
+ protocol_revision:client.getNegotiatedProtocolVersion()};
67
+ }finally{await client.close().catch(()=>{});}
68
+ }
69
+ const invoked=process.argv[1]&&path.resolve(process.argv[1])===fileURLToPath(import.meta.url);
70
+ if(invoked){const endpoint=process.argv[2];if(!endpoint)throw new Error('usage: node src/r40-compact-disarmed-probe.mjs https://host/mcp/path');
71
+ probeR40CompactDisarmed(endpoint).then(v=>console.log(JSON.stringify(v,null,2))).catch(e=>{console.error(e instanceof Error?e.stack:String(e));process.exitCode=1;});}
@@ -0,0 +1,34 @@
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 { R40_EXPECTED_TOOLS } from './remote-mcp-r40-parity-client.mjs';
9
+ const sha256=bytes=>createHash('sha256').update(bytes).digest('hex');
10
+ export async function probeR40FullSurface({packageRoot,generationRoot,stateRoot=path.join(os.homedir(),'.deadbyte-mcp')}={}){
11
+ const pkgRoot=packageRoot?path.resolve(packageRoot):path.resolve(path.dirname(fileURLToPath(import.meta.url)),'..');
12
+ const gen=path.resolve(generationRoot),semanticPath=path.join(gen,'semantic-lsp-config.json');
13
+ const semanticSha=sha256(await readFile(semanticPath));
14
+ const env={...getDefaultEnvironment(),DEADBYTE_STATE_ROOT:path.resolve(stateRoot),DEADBYTE_CORE_ROOT:pkgRoot,
15
+ DEADBYTE_WORKSPACE_ROOT:path.join(pkgRoot,'workspaces'),DEADBYTE_CAPABILITY_PROFILE:'full',DEADBYTE_AUDIT_RUNTIME:'0',
16
+ DEADBYTE_MACHINE_RUNTIME:'1',DEADBYTE_AGENT_RUNTIME:'1',DEADBYTE_PROCESS_RUNTIME:'1',DEADBYTE_PROCESS_POLICY:path.join(gen,'process-policy.json'),
17
+ DEADBYTE_CODING_RUNTIME:'1',DEADBYTE_MACHINE_FS_RUNTIME:'1',DEADBYTE_OBSERVATION_RUNTIME:'1',
18
+ DEADBYTE_DESKTOP_RUNTIME:'1',DEADBYTE_DESKTOP_POLICY:path.join(gen,'desktop-policy.json'),
19
+ DEADBYTE_CODING_POLICY:path.join(gen,'coding-policy.json'),DEADBYTE_SEMANTIC_LSP_CONFIG:semanticPath,DEADBYTE_SEMANTIC_LSP_CONFIG_SHA256:semanticSha,
20
+ DEADBYTE_AUTONOMOUS_RUNTIME:'1',DEADBYTE_AUTONOMOUS_POLICY:path.join(gen,'autonomous-policy.json'),
21
+ DEADBYTE_AUTONOMOUS_SUPERVISOR_MODE:'external',DEADBYTE_PRIVILEGED_POLICY:path.join(gen,'host-policy.json')};
22
+ const client=new Client({name:'deadbyte-r40-full-surface-probe',version:'0.12.0'},{versionNegotiation:{mode:{pin:'2026-07-28'}}});
23
+ const transport=new StdioClientTransport({command:process.execPath,args:[path.join(pkgRoot,'src','mcp-server.mjs')],cwd:pkgRoot,env,stderr:'pipe'});
24
+ try{
25
+ await client.connect(transport);const listed=await client.listTools(),names=listed.tools.map(t=>t.name).sort();
26
+ if(JSON.stringify(names)!==JSON.stringify(R40_EXPECTED_TOOLS))throw new Error('R40 full catalog mismatch count='+names.length);
27
+ for(const tool of listed.tools)if(tool.outputSchema?.type!=='object'||tool.outputSchema?.additionalProperties!==false||!tool.outputSchema?.properties)throw new Error('R40 full tool '+tool.name+' missing strict output schema');
28
+ return {status:'passed',profile:'full',tool_count:names.length,tools:names,catalog_sha256:sha256(Buffer.from(JSON.stringify(names),'utf8')),
29
+ strict_output_schemas:true,desktop_tools:['desktop_status','desktop_observe','desktop_target','desktop_action'],protocol_revision:client.getNegotiatedProtocolVersion()};
30
+ }finally{await client.close().catch(()=>{});}
31
+ }
32
+ if(process.argv[1]&&path.resolve(process.argv[1])===fileURLToPath(import.meta.url)){const [packageRoot,generationRoot]=process.argv.slice(2);
33
+ if(!packageRoot||!generationRoot)throw new Error('usage: node src/r40-full-surface-probe.mjs <package-root> <generation-root>');
34
+ probeR40FullSurface({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;});}
@@ -118,7 +118,31 @@ async function rebindProcessPolicy(raw,{developmentRoot,stateRoot,releaseRoot}){
118
118
  return policy;
119
119
  }
120
120
 
121
- function renderControllerConfig({currentConfig,coreRoot,workspaceRoot,processPolicyPath,codingPolicyPath,semanticConfigPath,semanticConfigSha256,autonomousPolicyPath,hostPolicyPath}){
121
+ async function rebindDesktopPolicy(raw,{stateRoot,releaseRoot}){
122
+ if(!raw) return null;
123
+ const policy=clone(raw);
124
+ if(policy.schema!=='deadbyte.desktop-policy.v1'||policy.enabled!==true) throw new Error('desktop policy schema/enabled mismatch');
125
+ policy.grant_file=path.join(stateRoot,'runtime','desktop-grant.json');
126
+ policy.evidence_dir=path.join(stateRoot,'runtime','desktop-evidence');
127
+ const providers={
128
+ observer:'deadbyte-desktop-observer-r40.exe',
129
+ input:'deadbyte-desktop-input-r40.exe',
130
+ grounder:'deadbyte-desktop-uia-r40.exe'
131
+ };
132
+ for(const [name,file] of Object.entries(providers)){
133
+ const provider=policy[name];
134
+ if(!provider||typeof provider.sha256!=='string'||!HEX64.test(provider.sha256)) throw new Error(`desktop ${name} policy pin invalid`);
135
+ const providerPath=path.join(releaseRoot,'bin',file);
136
+ const stat=await lstat(providerPath);
137
+ if(!stat.isFile()||stat.isSymbolicLink()) throw new Error(`desktop ${name} helper invalid in release slot`);
138
+ const actual=sha256(await readFile(providerPath));
139
+ if(actual!==provider.sha256) throw new Error(`desktop ${name} helper SHA-256 mismatch: policy=${provider.sha256} release=${actual}`);
140
+ provider.executable=providerPath;
141
+ }
142
+ return policy;
143
+ }
144
+
145
+ function renderControllerConfig({currentConfig,coreRoot,workspaceRoot,processPolicyPath,desktopPolicyPath,codingPolicyPath,semanticConfigPath,semanticConfigSha256,autonomousPolicyPath,hostPolicyPath}){
122
146
  const supervisor=currentConfig.Supervisor??{},bridge=currentConfig.Bridge??{},cloud=currentConfig.Cloud??{};
123
147
  return [
124
148
  '@{'," Schema = 'deadbyte.controller.v1'",` CoreRoot = ${psQuote(coreRoot)}`,
@@ -130,6 +154,8 @@ function renderControllerConfig({currentConfig,coreRoot,workspaceRoot,processPol
130
154
  ` CodingRuntimeEnabled = ${psBool(currentConfig.CodingRuntimeEnabled)}`,
131
155
  ` MachineFsRuntimeEnabled = ${psBool(currentConfig.MachineFsRuntimeEnabled)}`,
132
156
  ` ObservationRuntimeEnabled = ${psBool(currentConfig.ObservationRuntimeEnabled)}`,
157
+ ` DesktopRuntimeEnabled = ${psBool(currentConfig.DesktopRuntimeEnabled)}`,
158
+ ` DesktopPolicyFile = ${psQuote(desktopPolicyPath??'')}`,
133
159
  ` CodingPolicyFile = ${psQuote(codingPolicyPath)}`,
134
160
  ` SemanticLspConfigFile = ${psQuote(semanticConfigPath??'')}`,
135
161
  ` SemanticLspConfigSha256 = ${psQuote(semanticConfigSha256??'')}`,
@@ -172,7 +198,7 @@ function renderControllerConfig({currentConfig,coreRoot,workspaceRoot,processPol
172
198
  '}',''
173
199
  ].join('\r\n');
174
200
  }
175
- export async function createGenerationBundle({stateRoot,releaseRoot,developmentRoot,workspaceRoot,releaseId,currentConfig,processPolicy,codingPolicy,autonomousPolicy,hostPolicy}){
201
+ export async function createGenerationBundle({stateRoot,releaseRoot,developmentRoot,workspaceRoot,releaseId,currentConfig,processPolicy,desktopPolicy,codingPolicy,autonomousPolicy,hostPolicy}){
176
202
  const state=path.resolve(stateRoot),release=path.resolve(releaseRoot),development=path.resolve(developmentRoot),workspace=path.resolve(workspaceRoot);
177
203
  if(typeof releaseId!=='string'||!/^[A-Za-z0-9._-]{1,96}$/.test(releaseId)) throw new Error('generation release id invalid');
178
204
  if(typeof currentConfig?.Bridge?.FileSha256!=='string'||!HEX64.test(currentConfig.Bridge.FileSha256)) throw new Error('generation Bridge FileSha256 invalid');
@@ -230,6 +256,9 @@ export async function createGenerationBundle({stateRoot,releaseRoot,developmentR
230
256
  const hostBytes=host?jsonBytes(host):null;
231
257
  const processBound=await rebindProcessPolicy(processPolicy,{developmentRoot:development,stateRoot:state,releaseRoot:release});
232
258
  const processBytes=processBound?jsonBytes(processBound):null;
259
+ const desktopBound=await rebindDesktopPolicy(desktopPolicy,{stateRoot:state,releaseRoot:release});
260
+ const desktopBytes=desktopBound?jsonBytes(desktopBound):null;
261
+ if(currentConfig.DesktopRuntimeEnabled===true&&!desktopBytes) throw new Error('DesktopRuntimeEnabled requires desktop policy');
233
262
  const seedBody=canonical({
234
263
  release_id:releaseId,core_root:release,development_root:development,workspace_root:workspace,
235
264
  machine_runtime_enabled:currentConfig.MachineRuntimeEnabled!==false,
@@ -238,6 +267,7 @@ export async function createGenerationBundle({stateRoot,releaseRoot,developmentR
238
267
  coding_runtime_enabled:currentConfig.CodingRuntimeEnabled===true,
239
268
  machine_fs_runtime_enabled:currentConfig.MachineFsRuntimeEnabled===true,
240
269
  observation_runtime_enabled:currentConfig.ObservationRuntimeEnabled===true,
270
+ desktop_runtime_enabled:currentConfig.DesktopRuntimeEnabled===true,
241
271
  autonomous_runtime_enabled:currentConfig.AutonomousRuntimeEnabled===true,
242
272
  autonomous_supervisor_enabled:currentConfig.AutonomousSupervisorEnabled===true,
243
273
  supervisor:currentConfig.AutonomousSupervisorEnabled===true?(currentConfig.Supervisor??{}):null,
@@ -247,6 +277,7 @@ export async function createGenerationBundle({stateRoot,releaseRoot,developmentR
247
277
  start_timeout_seconds:Number(currentConfig.StartTimeoutSeconds??30),probe_timeout_seconds:Number(currentConfig.ProbeTimeoutSeconds??3),
248
278
  semantic_lsp_config_sha256:semanticConfigSha256,
249
279
  process_policy_sha256:processBytes?sha256(processBytes):null,
280
+ desktop_policy_sha256:desktopBytes?sha256(desktopBytes):null,
250
281
  coding_policy_sha256:sha256(codingBytes),autonomous_policy_sha256:sha256(autonomousBytes),host_policy_sha256:hostBytes?sha256(hostBytes):null
251
282
  });
252
283
  const generationSeed=sha256(Buffer.from(JSON.stringify(seedBody),'utf8'));
@@ -254,6 +285,7 @@ export async function createGenerationBundle({stateRoot,releaseRoot,developmentR
254
285
  const generationsRoot=path.join(state,'generations');
255
286
  const generationRoot=path.join(generationsRoot,generationId);
256
287
  const processPath=processBytes?path.join(generationRoot,'process-policy.json'):null;
288
+ const desktopPath=desktopBytes?path.join(generationRoot,'desktop-policy.json'):null;
257
289
  const codingPath=path.join(generationRoot,'coding-policy.json');
258
290
  const semanticPath=semanticBytes?path.join(generationRoot,'semantic-lsp-config.json'):null;
259
291
  const autonomousPath=path.join(generationRoot,'autonomous-policy.json');
@@ -262,15 +294,16 @@ export async function createGenerationBundle({stateRoot,releaseRoot,developmentR
262
294
  const files=new Map([
263
295
  ['coding-policy.json',codingBytes],
264
296
  ['autonomous-policy.json',autonomousBytes],
265
- ['controller-config.psd1',Buffer.from(renderControllerConfig({currentConfig,coreRoot:release,workspaceRoot:workspace,processPolicyPath:processPath,codingPolicyPath:codingPath,semanticConfigPath:semanticPath,semanticConfigSha256,autonomousPolicyPath:autonomousPath,hostPolicyPath:hostPath}),'utf8')]
297
+ ['controller-config.psd1',Buffer.from(renderControllerConfig({currentConfig,coreRoot:release,workspaceRoot:workspace,processPolicyPath:processPath,desktopPolicyPath:desktopPath,codingPolicyPath:codingPath,semanticConfigPath:semanticPath,semanticConfigSha256,autonomousPolicyPath:autonomousPath,hostPolicyPath:hostPath}),'utf8')]
266
298
  ]);
267
299
  if(semanticBytes) files.set('semantic-lsp-config.json',semanticBytes);
268
300
  if(processBytes) files.set('process-policy.json',processBytes);
301
+ if(desktopBytes) files.set('desktop-policy.json',desktopBytes);
269
302
  if(hostBytes) files.set('host-policy.json',hostBytes);
270
303
  const manifestBytes=generationManifest(files),manifestSha256=sha256(manifestBytes);
271
304
  if(existsSync(generationRoot)){
272
305
  const verified=await verifyGenerationBundle(generationRoot,manifestSha256);
273
- return {generation_id:generationId,generation_seed_sha256:generationSeed,generation_root:generationRoot,generation_manifest_sha256:manifestSha256,config_path:configPath,process_policy_path:processPath,coding_policy_path:codingPath,semantic_config_path:semanticPath,autonomous_policy_path:autonomousPath,host_policy_path:hostPath,adopted:true,...verified};
306
+ return {generation_id:generationId,generation_seed_sha256:generationSeed,generation_root:generationRoot,generation_manifest_sha256:manifestSha256,config_path:configPath,process_policy_path:processPath,desktop_policy_path:desktopPath,coding_policy_path:codingPath,semantic_config_path:semanticPath,autonomous_policy_path:autonomousPath,host_policy_path:hostPath,adopted:true,...verified};
274
307
  }
275
308
  await mkdir(generationsRoot,{recursive:true});
276
309
  const staging=path.join(generationsRoot,`.staging-${generationId}-${process.pid}-${randomBytes(4).toString('hex')}`);
@@ -285,5 +318,5 @@ export async function createGenerationBundle({stateRoot,releaseRoot,developmentR
285
318
  throw error;
286
319
  }
287
320
  const verified=await verifyGenerationBundle(generationRoot,manifestSha256);
288
- return {generation_id:generationId,generation_seed_sha256:generationSeed,generation_root:generationRoot,generation_manifest_sha256:manifestSha256,config_path:configPath,process_policy_path:processPath,coding_policy_path:codingPath,semantic_config_path:semanticPath,autonomous_policy_path:autonomousPath,host_policy_path:hostPath,adopted:false,...verified};
321
+ return {generation_id:generationId,generation_seed_sha256:generationSeed,generation_root:generationRoot,generation_manifest_sha256:manifestSha256,config_path:configPath,process_policy_path:processPath,desktop_policy_path:desktopPath,coding_policy_path:codingPath,semantic_config_path:semanticPath,autonomous_policy_path:autonomousPath,host_policy_path:hostPath,adopted:false,...verified};
289
322
  }
@@ -0,0 +1,41 @@
1
+ import path from 'node:path';
2
+ import { spawnSync } from 'node:child_process';
3
+
4
+ export const REMOTE_DISPLAY_MODES=Object.freeze(['unicode','ascii']);
5
+
6
+ function checkedMode(mode){
7
+ if(!REMOTE_DISPLAY_MODES.includes(mode)) throw new Error(`remote display mode invalid: ${String(mode)}`);
8
+ return mode;
9
+ }
10
+
11
+ export function remoteDisplayText(value,mode='unicode'){
12
+ checkedMode(mode);
13
+ const text=String(value??'');
14
+ if(mode==='unicode') return text;
15
+ let out='';
16
+ for(const ch of text){
17
+ const cp=ch.codePointAt(0);
18
+ if(cp===9||cp===10||cp===13||(cp>=0x20&&cp<=0x7e)) out+=ch;
19
+ else out+=`\\u{${cp.toString(16).toUpperCase()}}`;
20
+ }
21
+ return out;
22
+ }
23
+
24
+ export function prepareRemoteConsole({
25
+ platform=process.platform,
26
+ isTTY=Boolean(process.stdout?.isTTY),
27
+ env=process.env,
28
+ spawnSyncFn=spawnSync
29
+ }={}){
30
+ if(String(env?.DEADBYTE_REMOTE_ASCII??'')==='1') return Object.freeze({mode:'ascii',code_page:null,reason:'forced_ascii'});
31
+ if(platform!=='win32') return Object.freeze({mode:'unicode',code_page:null,reason:'non_windows'});
32
+ if(!isTTY) return Object.freeze({mode:'ascii',code_page:null,reason:'stdout_not_tty'});
33
+ const systemRoot=env?.SystemRoot||env?.WINDIR;
34
+ if(typeof systemRoot!=='string'||!systemRoot) return Object.freeze({mode:'ascii',code_page:null,reason:'system_root_missing'});
35
+ const chcp=path.win32.join(systemRoot,'System32','chcp.com');
36
+ let result;
37
+ try{ result=spawnSyncFn(chcp,['65001'],{stdio:'ignore',windowsHide:true,shell:false}); }
38
+ catch{ return Object.freeze({mode:'ascii',code_page:null,reason:'codepage_probe_threw'}); }
39
+ if(result?.error||result?.status!==0) return Object.freeze({mode:'ascii',code_page:null,reason:'codepage_65001_unavailable'});
40
+ return Object.freeze({mode:'unicode',code_page:65001,reason:'codepage_65001'});
41
+ }
@@ -1,5 +1,6 @@
1
1
  import { canonicalJson } from './canonical-json.mjs';
2
2
  import { sanitizeAuditArgs } from './tool-audit.mjs';
3
+ import { remoteDisplayText, REMOTE_DISPLAY_MODES } from './remote-console.mjs';
3
4
 
4
5
  const SAFE_ARG_KEYS=Object.freeze(['root_id','path','pattern','command_id','profile_id','session_id','goal_id','task_id','step_id','mode']);
5
6
  const LIFECYCLE=Object.freeze({
@@ -11,6 +12,15 @@ const LIFECYCLE=Object.freeze({
11
12
  ready:'✅ [READY] DEADBYTE MCP online',
12
13
  cleanup:'🔐 [AUTH] Revoking session authority...'
13
14
  });
15
+ const LIFECYCLE_ASCII=Object.freeze({
16
+ device_start:'[DEVICE] Starting DEADBYTE MCP...',
17
+ bridge_connected:'[BRIDGE] Local bridge connected',
18
+ cloud_connected:'[CLOUD] Remote MCP connected',
19
+ session_new:'[SESSION] New foreground session',
20
+ session_restored:'[SESSION] Previous result stream replayed',
21
+ ready:'[READY] DEADBYTE MCP online',
22
+ cleanup:'[AUTH] Revoking session authority...'
23
+ });
14
24
 
15
25
  function safeAtom(value){
16
26
  if(typeof value==='string'){
@@ -36,27 +46,97 @@ function summarizeArgs(args){
36
46
  const TOOL_COLUMN_WIDTH=32;
37
47
  const DISPLAY_UTC_OFFSET_MINUTES=7*60;
38
48
  const DISPLAY_UTC_OFFSET_LABEL='+07:00';
49
+ const HEARTBEAT_TOOLS=new Set(['machine_ping','machine_status','machine_system_status','machine_list']);
50
+ const TOOL_ICONS=Object.freeze({read:'📖',write:'📝',edit:'✏️',run:'🏃',verify:'🧾',search:'🔍',heartbeat:'📡',desktop:'🖥️',autonomous:'🤖',process:'⚙️',generic:'•'});
51
+ const TOOL_ICONS_ASCII=Object.freeze({read:'R',write:'W',edit:'E',run:'>',verify:'V',search:'S',heartbeat:'H',desktop:'D',autonomous:'A',process:'P',generic:'*'});
52
+ const START_VISIBLE_PATTERNS=[/(?:^|_)file_write$/,/text_replace$/,/symbol_replace$/,/native_diff_(?:apply|preview)$/,
53
+ /(?:command|benchmark)_run$/,/task_run$/,/goal_run$/,/goal_submit$/,/goal_approve$/,/process_session_(?:start|action|terminate)$/,/^desktop_action$/];
54
+
55
+ export function truncateUtf8(value,maxBytes,{ellipsis='…'}={}){
56
+ if(typeof value!=='string'||!Number.isInteger(maxBytes)||maxBytes<1) return '';
57
+ const bytes=new TextEncoder().encode(value);
58
+ if(bytes.length<=maxBytes) return value;
59
+ const suffix=new TextEncoder().encode(ellipsis);
60
+ const budget=Math.max(0,maxBytes-suffix.length);
61
+ let end=Math.min(budget,bytes.length);
62
+ while(end>0&&(bytes[end]&0xc0)===0x80) end--;
63
+ return new TextDecoder('utf-8',{fatal:false}).decode(bytes.subarray(0,end))+(suffix.length<=maxBytes?ellipsis:'');
64
+ }
65
+ function clipped(value,max=72){
66
+ if(typeof value!=='string') return '';
67
+ const one=value.replace(/[\r\n\t]+/g,' ').replace(/\s{2,}/g,' ').trim();
68
+ return truncateUtf8(one,max);
69
+ }
70
+ function shortValue(value,max=12){
71
+ if(typeof value!=='string'||!value) return '';
72
+ return value.length<=max?value:value.slice(0,max)+'…';
73
+ }
74
+ function payloadByteCount(value){
75
+ return value&&typeof value==='object'&&value.redacted===true&&Number.isFinite(Number(value.byte_count))?Number(value.byte_count):null;
76
+ }
77
+ function rootPath(args){
78
+ const root=safeAtom(args?.root_id);const p=safeAtom(args?.path);
79
+ if(root&&p) return root+':'+p;
80
+ return root||p||'';
81
+ }
82
+ export function summarizeLiveToolArgs(tool,rawArgs){
83
+ const args=sanitizeAuditArgs(rawArgs??{});const where=rootPath(args);
84
+ if(/(?:file_read|files_read)$/.test(tool)) return where;
85
+ if(/file_write$/.test(tool)){const bytes=payloadByteCount(args.content);return [where,bytes!==null?'('+bytes+'B)':''].filter(Boolean).join(' ');}
86
+ if(/text_replace$|symbol_replace$/.test(tool)){const bytes=payloadByteCount(args.new_string);return [where,bytes!==null?'('+bytes+'B new)':''].filter(Boolean).join(' ');}
87
+ if(/coding_tree$/.test(tool)) return where;
88
+ if(/coding_(?:search|inspect)$/.test(tool)) return [where,args.pattern?'\"'+clipped(String(args.pattern),36)+'\"':'',args.op?String(args.op):''].filter(Boolean).join(' ');
89
+ if(/(?:command|benchmark)_run$/.test(tool)) return [safeAtom(args.root_id),safeAtom(args.command_id),safeAtom(args.script_path)].filter(Boolean).join(' ');
90
+ if(/^machine_(?:ping|status|system_status|list)$/.test(tool)) return safeAtom(args.nonce)?'nonce='+shortValue(String(args.nonce),24):'';
91
+ if(/^process_session/.test(tool)) return [safeAtom(args.profile_id),shortValue(safeAtom(args.session_id)||'',12),safeAtom(args.op)].filter(Boolean).join(' ');
92
+ if(/^desktop_/.test(tool)) return [shortValue(safeAtom(args.frame_id)||'',10),shortValue(safeAtom(args.target_id)||'',10),safeAtom(args.op)].filter(Boolean).join(' ');
93
+ if(/^autonomous_/.test(tool)) return [shortValue(safeAtom(args.goal_id)||'',12),safeAtom(args.step_id)].filter(Boolean).join(' ');
94
+ return summarizeArgs(args).trim();
95
+ }
96
+ function categoryForTool(tool){
97
+ if(/^desktop_/.test(tool)) return 'desktop';
98
+ if(/^autonomous_/.test(tool)) return 'autonomous';
99
+ if(HEARTBEAT_TOOLS.has(tool)) return 'heartbeat';
100
+ if(/(?:receipt_verify|evidence_verify|goal_verify|task_verify|audit|release_gate|native_diff_verify)/.test(tool)) return 'verify';
101
+ if(/(?:search|inspect|symbol_(?:definition|references|implementations|outline|diagnostics)|_stat$|_tree$)/.test(tool)) return 'search';
102
+ if(/(?:file_read|files_read|machine_fs_read|process_session_read)/.test(tool)) return 'read';
103
+ if(/(?:file_write|machine_fs_write|mkdir|move|delete)/.test(tool)) return 'write';
104
+ if(/(?:replace|edit_exact|native_diff_apply)/.test(tool)) return 'edit';
105
+ if(/(?:command_run|benchmark_run|task_run|goal_run|process_session_(?:start|action|terminate))/.test(tool)) return 'run';
106
+ if(/^process_/.test(tool)) return 'process';
107
+ return 'generic';
108
+ }
109
+ function iconForTool(tool,displayMode='unicode'){
110
+ const icons=displayMode==='ascii'?TOOL_ICONS_ASCII:TOOL_ICONS;
111
+ return icons[categoryForTool(tool)]??icons.generic;
112
+ }
113
+ function visibleStart(tool){return START_VISIBLE_PATTERNS.some(pattern=>pattern.test(tool));}
39
114
 
40
115
  function displayTimestamp(ms){
41
116
  const shifted=new Date(ms+DISPLAY_UTC_OFFSET_MINUTES*60*1000).toISOString();
42
117
  return `${shifted.slice(0,-1)}${DISPLAY_UTC_OFFSET_LABEL}`;
43
118
  }
44
119
 
45
- function eventTimestamp(event,clock){
46
- for(const value of [event?.created_at_utc,event?.observed_at_utc]){
120
+ function eventMs(event,clock){
121
+ for(const value of [event?.ts,event?.created_at_utc,event?.observed_at_utc]){
47
122
  const parsed=Date.parse(value);
48
- if(typeof value==='string'&&Number.isFinite(parsed)) return displayTimestamp(parsed);
123
+ if(typeof value==='string'&&Number.isFinite(parsed)) return parsed;
49
124
  }
50
125
  const value=Number(clock());
51
- return displayTimestamp(Number.isFinite(value)?value:Date.now());
126
+ return Number.isFinite(value)?value:Date.now();
127
+ }
128
+ function eventTimestamp(event,clock){return displayTimestamp(eventMs(event,clock));}
129
+ function displayClock(ms){
130
+ const shifted=new Date(ms+DISPLAY_UTC_OFFSET_MINUTES*60*1000).toISOString();
131
+ return shifted.slice(11,23);
52
132
  }
53
133
 
54
- export function formatRemoteToolCall(event,{clock=()=>Date.now()}={}){
134
+ export function formatRemoteToolCall(event,{clock=()=>Date.now(),displayMode='unicode'}={}){
55
135
  if(!event||typeof event!=='object'||Array.isArray(event)||event.event_type!=='tool.request'||
56
136
  typeof event.tool!=='string'||!event.tool) return null;
57
137
  if(typeof clock!=='function') throw new Error('remote tool-call clock must be a function');
58
138
  const args=sanitizeAuditArgs(event.args??{});
59
- return `${eventTimestamp(event,clock)} | ${event.tool.padEnd(TOOL_COLUMN_WIDTH)} | Arguments: ${canonicalJson(args)}`;
139
+ return remoteDisplayText(`${eventTimestamp(event,clock)} | ${event.tool.padEnd(TOOL_COLUMN_WIDTH)} | Arguments: ${canonicalJson(args)}`,displayMode);
60
140
  }
61
141
 
62
142
  function formatDuration(ms){
@@ -77,9 +157,10 @@ export function formatCompactAuditEvent(event){
77
157
  return null;
78
158
  }
79
159
 
80
- export function formatRemoteLifecycle(kind){
160
+ export function formatRemoteLifecycle(kind,{displayMode='unicode'}={}){
81
161
  if(typeof kind!=='string'||!Object.hasOwn(LIFECYCLE,kind)) throw new Error(`unknown remote lifecycle '${String(kind)}'`);
82
- return LIFECYCLE[kind];
162
+ if(!REMOTE_DISPLAY_MODES.includes(displayMode)) throw new Error('remote display mode invalid');
163
+ return displayMode==='ascii'?LIFECYCLE_ASCII[kind]:LIFECYCLE[kind];
83
164
  }
84
165
 
85
166
  export function createLiveActivityTracker(){
@@ -109,56 +190,138 @@ function shortSpanId(requestId){
109
190
  return typeof requestId==='string'&&requestId.length?requestId.slice(0,8):'????????';
110
191
  }
111
192
 
112
- export function createLiveTimelineTracker({clock=()=>Date.now()}={}){
193
+ export function createLiveTimelineTracker({clock=()=>Date.now(),verbose=false,heartbeatBurstLimit=10,heartbeatWindowMs=3000,displayMode='unicode'}={}){
113
194
  if(typeof clock!=='function') throw new Error('live timeline clock must be a function');
195
+ if(!REMOTE_DISPLAY_MODES.includes(displayMode)) throw new Error('remote display mode invalid');
196
+ if(!Number.isInteger(heartbeatBurstLimit)||heartbeatBurstLimit<2||heartbeatBurstLimit>100) throw new Error('heartbeatBurstLimit invalid');
197
+ if(!Number.isInteger(heartbeatWindowMs)||heartbeatWindowMs<250||heartbeatWindowMs>60000) throw new Error('heartbeatWindowMs invalid');
114
198
  const pending=new Map();
115
199
  const completed=new Set();
116
- return Object.freeze({
117
- observe(event){
118
- if(!event||typeof event!=='object'||Array.isArray(event)) return null;
119
- const requestId=typeof event.request_id==='string'&&event.request_id?event.request_id:null;
120
- const tool=typeof event.tool==='string'&&event.tool?event.tool:null;
121
- if(event.event_type==='tool.request'&&requestId&&tool){
122
- if(pending.has(requestId)||completed.has(requestId)) return null;
123
- pending.set(requestId,{request_id:requestId,tool,started_at_ms:clock()});
124
- return formatRemoteToolCall(event,{clock});
125
- }
126
- if(event.event_type==='tool.result'&&requestId&&tool){
127
- if(completed.has(requestId)) return null;
128
- const span=pending.get(requestId)??null;
129
- pending.delete(requestId);completed.add(requestId);
130
- const error=event.outcome?.is_error===true;
131
- const duration=Number.isFinite(Number(event.outcome?.duration_ms))
132
- ? formatDuration(event.outcome.duration_ms)
133
- : span?formatDuration(Math.max(0,clock()-span.started_at_ms)):'';
134
- const marker=span?(error?'✗':'✓'):'↩';
135
- const status=error?'ERR':'OK ';
136
- const suffix=span?'':' (request before live window)';
137
- return `${marker} [${shortSpanId(requestId)}] ${status} ${tool}${duration?` ${duration}`:''}${suffix}`;
138
- }
200
+ const toolCounts=new Map();
201
+ let resultCount=0,errorCount=0,totalDurationMs=0,burst=null,foldedHeartbeatLines=0;
202
+
203
+ function eventPrefix(event,sequence){
204
+ const seq=Number.isSafeInteger(sequence)&&sequence>0?'#'+sequence+' ':'';
205
+ return iconForTool(event.tool,displayMode)+' '+displayClock(eventMs(event,clock))+' '+seq+'['+shortSpanId(event.request_id)+'] '+event.tool;
206
+ }
207
+ function resultLine(event,span,sequence){
208
+ const error=event.outcome?.is_error===true;
209
+ const unknown=event.outcome?.side_effect_state==='unknown';
210
+ const duration=Number.isFinite(Number(event.outcome?.duration_ms))
211
+ ?formatDuration(event.outcome.duration_ms)
212
+ :span?formatDuration(Math.max(0,clock()-span.started_at_ms)):'';
213
+ const summary=span?.summary||'';
214
+ const rawReason=typeof event.outcome?.reason==='string'&&event.outcome.reason?event.outcome.reason:
215
+ (unknown&&typeof event.outcome?.recovery?.reason==='string'?event.outcome.recovery.reason:'');
216
+ const reason=rawReason?clipped(rawReason,180):(unknown?'terminal result unavailable; inspect state before retry':'');
217
+ const marker=unknown?'OUTCOME_UNKNOWN':error?(displayMode==='ascii'?'ERR':'✗'):(displayMode==='ascii'?'OK':'✓');
218
+ return remoteDisplayText(eventPrefix(event,sequence)+(summary?' '+summary:'')+(duration?' '+duration:'')+' '+marker+(reason?' — '+reason:''),displayMode);
219
+ }
220
+ function burstLine(value){
221
+ if(!value) return null;
222
+ const avg=value.count?value.total_ms/value.count:0;
223
+ const seq=Number.isSafeInteger(value.last_sequence)&&value.last_sequence>0?'#'+value.last_sequence+' ':'';
224
+ const startClock=displayClock(value.first_ms);
225
+ const endClock=displayClock(value.last_ms);
226
+ const range=value.first_ms===value.last_ms?endClock:startClock.slice(0,8)+'–'+endClock.slice(0,8);
227
+ if(value.count>1) foldedHeartbeatLines+=value.count-1;
228
+ const marker=displayMode==='ascii'?'OK':'✓';
229
+ return remoteDisplayText(iconForTool(value.tool,displayMode)+' '+range+' '+seq+value.tool+' ×'+value.count+' avg '+formatDuration(avg)+' '+marker,displayMode);
230
+ }
231
+ function flushBurst(){
232
+ const line=burstLine(burst);
233
+ burst=null;
234
+ return line;
235
+ }
236
+ function addStats(event){
237
+ resultCount++;
238
+ toolCounts.set(event.tool,(toolCounts.get(event.tool)??0)+1);
239
+ const duration=Number(event.outcome?.duration_ms);
240
+ if(Number.isFinite(duration)&&duration>=0) totalDurationMs+=duration;
241
+ if(event.outcome?.is_error===true) errorCount++;
242
+ }
243
+ function observe(event,{sequence=null}={}){
244
+ if(!event||typeof event!=='object'||Array.isArray(event)) return null;
245
+ const requestId=typeof event.request_id==='string'&&event.request_id?event.request_id:null;
246
+ const tool=typeof event.tool==='string'&&event.tool?event.tool:null;
247
+ if(event.event_type==='tool.request'&&requestId&&tool){
248
+ if(pending.has(requestId)||completed.has(requestId)) return null;
249
+ const summary=summarizeLiveToolArgs(tool,event.args??{});
250
+ pending.set(requestId,{request_id:requestId,tool,summary,started_at_ms:clock()});
251
+ if(verbose) return formatRemoteToolCall(event,{clock,displayMode});
252
+ if(visibleStart(tool)) return eventPrefix(event,sequence)+(summary?' '+summary:'')+' …';
139
253
  return null;
140
- },
141
- pending(){return [...pending.values()].map(({request_id,tool})=>({request_id,tool}));},
142
- reconcileInterrupted(reason='session_end'){
143
- const suffix=reason==='recovery'?'recovery ended before terminal result':
144
- reason==='session_end'?'session ended before terminal result':'stream ended before terminal result';
145
- const lines=[...pending.values()].map(item=>`⚠ [${shortSpanId(item.request_id)}] INTERRUPTED ${item.tool} — ${suffix}`);
146
- pending.clear();
147
- return lines;
148
254
  }
255
+ if(event.event_type==='tool.result'&&requestId&&tool){
256
+ if(completed.has(requestId)) return null;
257
+ const span=pending.get(requestId)??null;
258
+ pending.delete(requestId);
259
+ completed.add(requestId);
260
+ addStats(event);
261
+ const error=event.outcome?.is_error===true;
262
+ const nowMs=eventMs(event,clock);
263
+ if(HEARTBEAT_TOOLS.has(tool)&&!error){
264
+ const duration=Number(event.outcome?.duration_ms);
265
+ if(burst&&burst.tool===tool&&nowMs-burst.last_ms<=heartbeatWindowMs){
266
+ burst.count++;
267
+ burst.last_ms=nowMs;
268
+ burst.last_sequence=sequence;
269
+ if(Number.isFinite(duration)&&duration>=0) burst.total_ms+=duration;
270
+ }else{
271
+ const prior=flushBurst();
272
+ burst={tool,count:1,total_ms:Number.isFinite(duration)&&duration>=0?duration:0,
273
+ first_ms:nowMs,last_ms:nowMs,first_sequence:sequence,last_sequence:sequence};
274
+ return prior;
275
+ }
276
+ if(burst.count>=heartbeatBurstLimit) return flushBurst();
277
+ return null;
278
+ }
279
+ const lines=[];
280
+ const prior=flushBurst();
281
+ if(prior) lines.push(prior);
282
+ lines.push(resultLine(event,span,sequence));
283
+ return lines.length===1?lines[0]:lines;
284
+ }
285
+ return null;
286
+ }
287
+ function reconcileInterrupted(reason='session_end'){
288
+ const suffix=reason==='recovery'?'recovery ended before terminal result':
289
+ reason==='session_end'?'session ended before terminal result':'stream ended before terminal result';
290
+ const lines=[];
291
+ const folded=flushBurst();
292
+ if(folded) lines.push(folded);
293
+ for(const item of pending.values()) lines.push(remoteDisplayText((displayMode==='ascii'?'[WARN]':'⚠')+' ['+shortSpanId(item.request_id)+'] OUTCOME_UNKNOWN '+item.tool+' — '+suffix+'; inspect state before retry',displayMode));
294
+ pending.clear();
295
+ return lines;
296
+ }
297
+ function summary(){
298
+ const top=[...toolCounts.entries()].sort((a,b)=>b[1]-a[1]||a[0].localeCompare(b[0])).slice(0,3)
299
+ .map(([tool,count])=>tool+'×'+count).join(', ');
300
+ return remoteDisplayText((displayMode==='ascii'?'[SUMMARY]':'📊 [SUMMARY]')+' '+resultCount+' calls | '+errorCount+' error'+(errorCount===1?'':'s')+
301
+ ' | tool time '+(formatDuration(totalDurationMs)||'0ms')+
302
+ (foldedHeartbeatLines?' | folded '+foldedHeartbeatLines+' heartbeat lines':'')+
303
+ (top?' | top: '+top:''),displayMode);
304
+ }
305
+ return Object.freeze({
306
+ observe,
307
+ pending(){return [...pending.values()].map(({request_id,tool})=>({request_id,tool}));},
308
+ reconcileInterrupted,
309
+ summary
149
310
  });
150
311
  }
151
312
 
152
-
153
- export function formatRuntimeDiagnosticLine(file,line){
313
+ export function formatRuntimeDiagnosticLine(file,line,{verbose=false,displayMode='unicode'}={}){
154
314
  if(typeof file!=='string'||typeof line!=='string'||!line) return null;
155
315
  const label=file.split(/[\\/]/).at(-1).replace(/\.log$/i,'').toUpperCase();
156
316
  const input=line.match(/\[TOOL IN \]\s+\S+\s+#\d+\s+([A-Za-z0-9_.-]+)/);
157
- if(input) return `[${label}] IN ${input[1]}`;
317
+ if(input) return verbose?remoteDisplayText('['+label+'.DEBUG] IN '+input[1],displayMode):null;
158
318
  const output=line.match(/\[TOOL OUT\]\s+\S+\s+#\d+\s+([A-Za-z0-9_.-]+)/);
159
319
  if(output){
160
- if(/"is_error"\s*:\s*true/.test(line)) return `[${label}] ERR ${output[1]}`;
161
- return `[${label}] OUT ${output[1]}`;
320
+ if(!verbose) return null;
321
+ if(/"is_error"\s*:\s*true/.test(line)) return remoteDisplayText('['+label+'.DEBUG] ERR '+output[1],displayMode);
322
+ return remoteDisplayText('['+label+'.DEBUG] OUT '+output[1],displayMode);
162
323
  }
324
+ const compact=line.match(/^\[TOOL\]\s+(\S+)\s+#(\d+)\s+(✓|✗|OK|ERR)\s+([A-Za-z0-9_.-]+)\s+([^\s]+)(?:\s+—\s+(.+))?$/u);
325
+ if(compact) return verbose?remoteDisplayText('['+label+'.DEBUG] #'+compact[2]+' '+compact[3]+' '+compact[4]+' '+compact[5]+(compact[6]?' — '+compact[6]:''),displayMode):null;
163
326
  return null;
164
327
  }