beeos-claw 0.6.66-cloud.0 → 0.6.66-cloud.2

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 (38) hide show
  1. package/README.md +13 -0
  2. package/dist/acp-gateway/bridge-core.js +1 -1
  3. package/dist/acp-server.js +1 -1
  4. package/dist/contracts/question/codec.js +1 -0
  5. package/dist/gateway-client.js +1 -1
  6. package/dist/im-inbound-handler.js +1 -1
  7. package/dist/im-runtime-dispatch.js +1 -1
  8. package/dist/index.js +1 -1
  9. package/dist/message-projection-inbound-consumer.js +1 -1
  10. package/dist/runtime/agent-projection-production.js +1 -1
  11. package/dist/runtime/conversation-title.js +1 -1
  12. package/dist/runtime/core-execution-attempt-context.js +1 -1
  13. package/dist/runtime/im-reply-sink.js +1 -1
  14. package/dist/runtime/message-service-runtime-invocation-authority.js +1 -1
  15. package/dist/runtime/message-service-session-reasoning-http-authority.js +1 -0
  16. package/dist/runtime/ms-runtime-message-channel-adapter.js +1 -1
  17. package/dist/runtime/openclaw-native-sessions.js +1 -1
  18. package/dist/runtime/openclaw-question-adapter.js +1 -0
  19. package/dist/runtime/openclaw-runtime-capabilities.js +1 -1
  20. package/dist/runtime/openclaw-title-provider.js +1 -0
  21. package/dist/runtime/question-wire.js +1 -0
  22. package/dist/runtime/reply-router.js +1 -1
  23. package/dist/runtime/requested-model.js +1 -0
  24. package/dist/runtime/runtime-command-dispatcher.js +1 -1
  25. package/dist/runtime/runtime-command-journal.js +1 -1
  26. package/dist/runtime/runtime-parity-manifest-digest.js +1 -1
  27. package/dist/runtime/runtime-parity-manifest.js +1 -1
  28. package/dist/runtime/runtime-production-authority.js +1 -1
  29. package/dist/runtime/runtime-reasoning.js +1 -0
  30. package/dist/runtime/runtime-service-contract-compatibility.js +1 -1
  31. package/dist/runtime/runtime-service-production-candidate.js +1 -1
  32. package/dist/runtime/runtime-session-control-service.js +1 -1
  33. package/dist/runtime/session-set-model-mapping.js +1 -1
  34. package/dist/terminal/runtime-terminal-outbound-registration.js +1 -1
  35. package/dist/terminal/runtime-terminal-service.js +1 -1
  36. package/dist/terminal/terminal-lease-bound-bridge-client.js +1 -1
  37. package/package.json +7 -6
  38. package/runtime-service-wire-identity.v1.json +12 -12
@@ -1 +1 @@
1
- import{TERMINAL_SERVICE_PROTOCOL_VERSION}from"@beeos-ai/beeos-types/runtime";import{TerminalNotFoundError}from"./session-manager.js";const MAX_OUTPUT_FRAME_BYTES=16384,OUTPUT_FLUSH_INTERVAL_MS=16;export class RuntimeTerminalService{manager;clients=new Map;clientSequences=new Map;openingFrames=new Map;pendingOutput=new Map;outboundGeneration;constructor(e,n){this.manager=e,n&&this.setOutbound(n),e.addListeners({onOutput:e=>{const n=this.clients.get(e.terminalId);"service"===n?.kind&&n.clientId===e.sessionId&&n.incarnation===e.incarnation&&this.manager.isCurrentSession(e.terminalId,n.incarnation)&&this.queueOutput(e.terminalId,e.stream,e.data,e.seq,n)},onExit:e=>{const n=this.clients.get(e.terminalId);if("acp"===n?.kind&&n.clientId===e.sessionId&&n.incarnation===e.incarnation){const t=setTimeout(()=>{this.clients.get(e.terminalId)===n&&(this.clients.delete(e.terminalId),this.manager.releaseSessionIfCurrent(e.terminalId,n.incarnation))},0);return void t.unref?.()}"service"===n?.kind&&n.clientId===e.sessionId&&n.incarnation===e.incarnation&&this.manager.isCurrentSession(e.terminalId,n.incarnation)&&(this.flushPendingOutput(e.terminalId),this.emitFrame({protocolVersion:TERMINAL_SERVICE_PROTOCOL_VERSION,type:"terminal.exit",clientId:n.clientId,terminalId:e.terminalId,sequence:String(e.seq),...null===e.code?{}:{exitCode:e.code},...null===e.signal?{}:{signal:e.signal}},n))},onStateChange:e=>{if("closing"!==e.to)return;const n=this.clients.get(e.terminalId);if("acp"===n?.kind&&n.clientId===e.sessionId&&n.incarnation===e.incarnation){const t=setTimeout(()=>{this.clients.get(e.terminalId)!==n||this.manager.isCurrentSession(e.terminalId,e.incarnation)||this.clients.delete(e.terminalId)},0);return void t.unref?.()}"service"===n?.kind&&n.clientId===e.sessionId&&n.incarnation===e.incarnation&&this.isOwnedTerminal(n)&&n.generation.outbound.startTerminalReplayExpiry(n.clientId,e.terminalId)}})}setOutbound(e){if(this.outboundGeneration?.outbound===e)return this.outboundGeneration;if(this.outboundGeneration)throw new Error("Terminal outbound sink is already configured");const n=Object.freeze({outbound:e});return this.outboundGeneration=n,n}clearOutbound(e,n){const t=n??this.outboundGeneration;t&&t.outbound===e&&this.outboundGeneration===t&&(this.outboundGeneration=void 0,this.clearGenerationOwnership(t))}fenceServiceSessions(e="runtime_lease_lost",n=this.outboundGeneration){for(const[t,i]of this.clients)"service"!==i.kind||n&&i.generation!==n||this.manager.closeSessionIfCurrent(t,i.incarnation,e);if(n)this.clearGenerationOwnership(n);else{for(const[e,n]of this.clients)"service"===n.kind&&this.clients.delete(e);this.clientSequences.clear();for(const[e,n]of this.openingFrames)"service"===n.owner.kind&&this.openingFrames.delete(e)}}async handleClientFrame(e,n=this.outboundGeneration,t){if(!n||!this.isGenerationActive(n))throw new Error("terminal outbound generation is inactive");assertTerminalClientFrame(e);const i=BigInt(e.clientSequence),r=this.clientSequences.get(e.clientId);if(i<=(r?.generation===n?r.sequence:0n))throw new Error("non-monotonic terminal client sequence");if(this.clientSequences.set(e.clientId,{generation:n,sequence:i}),"terminal.open"===e.type||"terminal.create"===e.type){if(t?.recovered&&!this.clients.has(e.terminalId)&&!this.manager.getSession(e.terminalId))throw new TerminalNotFoundError(e.terminalId);if(this.clients.has(e.terminalId)||this.manager.getSession(e.terminalId)){const i=this.clients.get(e.terminalId);if(t?.recovered&&"service"===i?.kind&&i.clientId===e.clientId&&i.generation===n&&this.isOwnedTerminal(i)){const n=this.manager.getSession(e.terminalId);return void this.sendOrClose({protocolVersion:TERMINAL_SERVICE_PROTOCOL_VERSION,type:"terminal.opened",clientId:e.clientId,terminalId:e.terminalId,sequence:this.nextSequence(i),resumed:!0,columns:n.cols,rows:n.rows},i)}throw new Error("terminal already exists; use terminal.resume")}const i=this.manager.createSessionIncarnation(e.terminalId),r={kind:"service",clientId:e.clientId,generation:n,incarnation:i};this.clients.set(e.terminalId,r),this.openingFrames.set(e.terminalId,{owner:r,frames:[]});try{const n=this.manager.openSession({terminalId:e.terminalId,sessionId:e.clientId,command:e.command,args:e.args,env:e.env,cwd:e.cwd,cols:"terminal.open"===e.type?e.columns??80:80,rows:"terminal.open"===e.type?e.rows??24:24,outputLimitBytes:e.outputLimitBytes,initialServerSequence:2,incarnation:i});if(!this.isOwnedTerminal(r))return void this.manager.releaseSessionIfCurrent(e.terminalId,i);this.sendOrClose({protocolVersion:TERMINAL_SERVICE_PROTOCOL_VERSION,type:"terminal.opened",clientId:e.clientId,terminalId:n.terminalId,sequence:"1",resumed:!0===t?.recovered,columns:n.cols,rows:n.rows},r);const s=this.openingFrames.get(e.terminalId);s?.owner===r&&this.openingFrames.delete(e.terminalId);for(const e of s?.owner===r?s.frames:[])this.sendOrClose(e,r)}catch(n){throw this.clients.get(e.terminalId)===r&&this.clients.delete(e.terminalId),this.openingFrames.get(e.terminalId)?.owner===r&&this.openingFrames.delete(e.terminalId),this.manager.releaseSessionIfCurrent(e.terminalId,i),n}return}"terminal.resume"===e.type&&this.rebindServiceOwnerForResume(e.clientId,e.terminalId,n);const s=this.assertOwner(e.clientId,e.terminalId,n);switch(e.type){case"terminal.resume":{const t=n.outbound.resume(e.clientId,e.terminalId,e.lastServerSequence);if(!this.isOwnedTerminal(s))return;if(t.gap&&this.sendOrClose({protocolVersion:TERMINAL_SERVICE_PROTOCOL_VERSION,type:"terminal.output",clientId:e.clientId,terminalId:e.terminalId,sequence:this.nextSequence(s),stream:"stdout",data:"",encoding:"base64",truncated:!0},s),!this.isOwnedTerminal(s))return;const i=this.manager.getSession(e.terminalId);return void this.sendOrClose({protocolVersion:TERMINAL_SERVICE_PROTOCOL_VERSION,type:"terminal.opened",clientId:e.clientId,terminalId:e.terminalId,sequence:this.nextSequence(s),resumed:!0,columns:i.cols,rows:i.rows},s)}case"terminal.input":return void this.manager.writeToSessionIfCurrent(e.terminalId,s.incarnation,Buffer.from(e.data,"base64"===e.encoding?"base64":"utf8"));case"terminal.resize":return void this.manager.resizeSessionIfCurrent(e.terminalId,s.incarnation,e.columns,e.rows);case"terminal.close":if(!this.manager.closeSessionIfCurrent(e.terminalId,s.incarnation,e.reason))throw new TerminalNotFoundError(e.terminalId);return;case"terminal.ping":if(this.manager.touchSessionIfCurrent(e.terminalId,s.incarnation),!this.isOwnedTerminal(s))return;return void this.sendOrClose({protocolVersion:TERMINAL_SERVICE_PROTOCOL_VERSION,type:"terminal.pong",clientId:e.clientId,terminalId:e.terminalId,sequence:this.nextSequence(s),nonce:e.nonce},s);case"terminal.wait":{let n;try{n=await this.manager.waitForExitIfCurrent(e.terminalId,s.incarnation,e.timeoutMs)}catch(e){if(!this.isOwnedTerminal(s))return;throw e}if(!this.isOwnedTerminal(s))return;return void this.sendOrClose({protocolVersion:TERMINAL_SERVICE_PROTOCOL_VERSION,type:"terminal.wait_result",clientId:e.clientId,terminalId:e.terminalId,sequence:this.nextSequence(s),exitCode:n.exitCode,output:n.output,state:n.state},s)}case"terminal.release":if(!this.manager.isCurrentSession(e.terminalId,s.incarnation))throw new TerminalNotFoundError(e.terminalId);const t=n.outbound.prepareForgetTerminal(e.clientId,e.terminalId);if(!this.manager.detachSessionIfCurrent(e.terminalId,s.incarnation))throw new TerminalNotFoundError(e.terminalId);return this.clients.get(e.terminalId)===s&&this.clients.delete(e.terminalId),this.dropPendingOutput(e.terminalId),void t();case"terminal.kill":if(!this.manager.killSessionIfCurrent(e.terminalId,s.incarnation,normalizeSignal(e.signal)))throw new TerminalNotFoundError(e.terminalId);return}}async execute(e,n){const t=this.outboundGeneration,i=t?.outbound;if(i?.isAvailable&&!i.isAvailable())throw new Error("terminal Service transport is unavailable; legacy fallback is forbidden");const r=e,s=r.context.terminal.terminalId,a=r.context.terminal.clientId;switch(r.name){case"terminal.open":{const e=r,n=this.manager.createSessionIncarnation(s),t=this.manager.openSession({terminalId:s,sessionId:e.context.terminal.clientId,command:e.params.command,args:e.params.args,env:e.params.env,cwd:e.params.cwd,cols:e.params.columns,rows:e.params.rows,outputLimitBytes:e.params.outputLimitBytes,incarnation:n});return this.clients.set(t.terminalId,{kind:"acp",clientId:e.context.terminal.clientId,incarnation:n}),{terminalId:t.terminalId,command:e.params.command??t.shell,shell:t.shell,cwd:t.cwd,columns:t.cols,rows:t.rows,state:t.state}}case"terminal.input":{const e=r,n=this.assertAcpOwner(a,s);return this.manager.writeToSessionIfCurrent(s,n.incarnation,Buffer.from(e.params.data,"base64"===e.params.encoding?"base64":"utf8")),{accepted:!0}}case"terminal.resize":{const e=r,n=this.assertAcpOwner(a,s);return this.manager.resizeSessionIfCurrent(s,n.incarnation,e.params.columns,e.params.rows),{resized:!0}}case"terminal.close":{const e=this.assertAcpOwner(a,s);if(!this.manager.closeSessionIfCurrent(s,e.incarnation,r.params.reason))throw new TerminalNotFoundError(s);return{closed:!0}}case"terminal.ping":{const e=this.assertAcpOwner(a,s);return this.manager.touchSessionIfCurrent(s,e.incarnation),{terminalId:s,status:"ok"}}case"terminal.create":{const e=r,n=this.manager.createSessionIncarnation(s),t=this.manager.openSession({terminalId:s,sessionId:e.context.terminal.clientId,command:e.params.command,args:e.params.args,env:e.params.env,cwd:e.params.cwd,cols:80,rows:24,outputLimitBytes:e.params.outputLimitBytes,incarnation:n});return this.clients.set(t.terminalId,{kind:"acp",clientId:e.context.terminal.clientId,incarnation:n}),{terminalId:t.terminalId,command:e.params.command}}case"terminal.wait":{const e=r,n=this.assertAcpOwner(a,s),t=await this.manager.waitForExitIfCurrent(s,n.incarnation,e.params.timeoutMs);if(!this.isOwnedAcpTerminal(n))throw new TerminalNotFoundError(s);return t}case"terminal.release":{const e=this.assertAcpOwner(a,s);if(!this.manager.detachSessionIfCurrent(s,e.incarnation))throw new TerminalNotFoundError(s);return this.clients.get(s)===e&&this.clients.delete(s),{released:!0}}case"terminal.kill":{const e=this.assertAcpOwner(a,s);if(!this.manager.killSessionIfCurrent(s,e.incarnation,normalizeSignal(r.params.signal)))throw new TerminalNotFoundError(s);return{killed:!0}}}throw new Error(`unsupported terminal command ${r.name}`)}nextSequence(e){return String(this.manager.nextServerSequence(e.incarnation.terminalId,e.incarnation))}sendOrClose(e,n){this.isOwnedTerminal(n)&&!n.generation.outbound.send(e)&&"terminal.output"!==e.type&&this.isOwnedTerminal(n)&&this.manager.closeSessionIfCurrent(e.terminalId,n.incarnation,"backpressure_limit")}emitFrame(e,n){if(!this.isOwnedTerminal(n))return;const t=this.openingFrames.get(e.terminalId);t?.owner===n?t.frames.push(e):this.sendOrClose(e,n)}assertOwner(e,n,t){const i=this.clients.get(n);if("service"!==i?.kind||i.clientId!==e||i.generation!==t||!this.isOwnedTerminal(i))throw new TerminalNotFoundError(n);return i}rebindServiceOwnerForResume(e,n,t){const i=this.clients.get(n);if("service"!==i?.kind||i.generation!==t||!this.isOwnedTerminal(i))return;if(i.clientId===e)return;if(this.flushPendingOutput(n),!this.manager.rebindSessionId(n,i.incarnation,e))throw new TerminalNotFoundError(n);t.outbound.rebindResumeClient(i.clientId,e,n),this.clientSequences.delete(i.clientId);const r={kind:"service",clientId:e,generation:i.generation,incarnation:i.incarnation};this.clients.set(n,r)}assertAcpOwner(e,n){const t=this.clients.get(n);if("acp"!==t?.kind||t.clientId!==e||!this.isOwnedAcpTerminal(t))throw new TerminalNotFoundError(n);return t}isGenerationActive(e){return this.outboundGeneration===e}isOwnedTerminal(e){return this.outboundGeneration===e.generation&&this.clients.get(e.incarnation.terminalId)===e&&this.manager.isCurrentSession(e.incarnation.terminalId,e.incarnation)}isOwnedAcpTerminal(e){return this.clients.get(e.incarnation.terminalId)===e&&this.manager.isCurrentSession(e.incarnation.terminalId,e.incarnation)}expireServiceTerminalReplay(e,n,t){const i=this.clients.get(n);if("service"!==i?.kind||i.clientId!==e||i.generation!==t||!this.isOwnedTerminal(i))return;const r=t.outbound.prepareForgetTerminal(e,n);if(!("closed"===this.manager.getSession(n)?.state?this.manager.releaseSessionIfCurrent(n,i.incarnation):this.manager.detachSessionIfCurrent(n,i.incarnation)))throw new TerminalNotFoundError(n);this.clients.get(n)===i&&this.clients.delete(n),this.openingFrames.get(n)?.owner===i&&this.openingFrames.delete(n),this.dropPendingOutput(n),r()}clearGenerationOwnership(e){for(const[n,t]of this.clients)"service"===t.kind&&t.generation===e&&this.clients.delete(n);for(const[n,t]of this.clientSequences)t.generation===e&&this.clientSequences.delete(n);for(const[n,t]of this.openingFrames)t.owner.generation===e&&this.openingFrames.delete(n);for(const[n,t]of this.pendingOutput)t.owner.generation===e&&this.dropPendingOutput(n)}queueOutput(e,n,t,i,r){let s=0,a=i;for(;s<t.length;){let i=this.pendingOutput.get(e);!i||i.owner===r&&i.stream===n||(this.flushPendingOutput(e),i=void 0),i||(i={owner:r,stream:n,chunks:[],bytes:0,sequence:a},this.pendingOutput.set(e,i));const o=Math.min(16384-i.bytes,t.length-s);i.chunks.push(t.subarray(s,s+o)),i.bytes+=o,i.sequence=a,s+=o,16384!==i.bytes?i.timer||(i.timer=setTimeout(()=>{this.pendingOutput.get(e)===i&&this.flushPendingOutput(e)},16),i.timer.unref?.()):(this.flushPendingOutput(e),s<t.length&&(a=this.manager.nextServerSequence(e,r.incarnation)))}}flushPendingOutput(e){const n=this.pendingOutput.get(e);if(!n)return;if(this.pendingOutput.delete(e),n.timer&&clearTimeout(n.timer),0===n.bytes||!this.isOwnedTerminal(n.owner))return;const t=1===n.chunks.length?n.chunks[0]:Buffer.concat(n.chunks,n.bytes);this.emitFrame({protocolVersion:TERMINAL_SERVICE_PROTOCOL_VERSION,type:"terminal.output",clientId:n.owner.clientId,terminalId:e,sequence:String(n.sequence),stream:n.stream,data:t.toString("base64"),encoding:"base64"},n.owner)}dropPendingOutput(e){const n=this.pendingOutput.get(e);n&&(n.timer&&clearTimeout(n.timer),this.pendingOutput.delete(e))}}const runtimeServices=new WeakMap;export function getOrCreateRuntimeTerminalService(e){let n=runtimeServices.get(e);return n||(n=new RuntimeTerminalService(e),runtimeServices.set(e,n)),n}export function assertTerminalClientFrame(e){const n=e;if(!(n&&CLIENT_FRAME_TYPES.has(String(n.type))&&n.protocolVersion===TERMINAL_SERVICE_PROTOCOL_VERSION&&isUuid(n.clientId)&&isUuid(n.terminalId)&&isPositiveRuntimeSequence(n.clientSequence)))throw new Error("invalid terminal client frame");switch(e.type){case"terminal.open":assertOptionalCommand(e.command),assertOptionalArgs(e.args),assertOptionalEnv(e.env),assertOptionalString(e.cwd),assertOptionalDimension(e.columns),assertOptionalDimension(e.rows),assertOptionalOutputLimit(e.outputLimitBytes);break;case"terminal.create":("string"!=typeof e.command||e.command.length<1||e.command.length>4096)&&invalidFrame(),assertOptionalArgs(e.args),assertOptionalEnv(e.env),assertOptionalString(e.cwd),assertOptionalOutputLimit(e.outputLimitBytes);break;case"terminal.resume":isRuntimeSequence(e.lastServerSequence)||invalidFrame();break;case"terminal.input":("string"!=typeof e.data||"utf8"!==e.encoding&&"base64"!==e.encoding)&&invalidFrame();break;case"terminal.resize":assertDimension(e.columns),assertDimension(e.rows);break;case"terminal.close":assertOptionalString(e.reason);break;case"terminal.ping":"string"!=typeof e.nonce&&invalidFrame();break;case"terminal.wait":void 0!==e.timeoutMs&&(!Number.isInteger(e.timeoutMs)||e.timeoutMs<0||e.timeoutMs>864e5)&&invalidFrame();break;case"terminal.kill":assertOptionalString(e.signal)}}const CLIENT_FRAME_TYPES=new Set(["terminal.open","terminal.resume","terminal.input","terminal.resize","terminal.close","terminal.ping","terminal.create","terminal.wait","terminal.release","terminal.kill"]);function normalizeSignal(e){return e&&e.startsWith("SIG")?e:"SIGTERM"}const UUID_PATTERN=/^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i,UINT64_MAX=18446744073709551615n;function isUuid(e){return"string"==typeof e&&UUID_PATTERN.test(e)}function isRuntimeSequence(e){return"string"==typeof e&&/^(0|[1-9][0-9]*)$/.test(e)&&BigInt(e)<=UINT64_MAX}function isPositiveRuntimeSequence(e){return isRuntimeSequence(e)&&"0"!==e}function invalidFrame(){throw new Error("invalid terminal client frame")}function assertOptionalString(e){void 0!==e&&"string"!=typeof e&&invalidFrame()}function assertOptionalCommand(e){void 0!==e&&("string"!=typeof e||e.length<1||e.length>4096)&&invalidFrame()}function assertOptionalArgs(e){void 0===e||Array.isArray(e)&&e.every(e=>"string"==typeof e)||invalidFrame()}function assertOptionalEnv(e){void 0===e||e&&"object"==typeof e&&!Array.isArray(e)&&Object.values(e).every(e=>"string"==typeof e)||invalidFrame()}function assertDimension(e){(!Number.isInteger(e)||e<1||e>1e3)&&invalidFrame()}function assertOptionalDimension(e){void 0!==e&&assertDimension(e)}function assertOptionalOutputLimit(e){void 0!==e&&(!Number.isInteger(e)||e<1||e>16777216)&&invalidFrame()}
1
+ import{TERMINAL_SERVICE_PROTOCOL_VERSION}from"@beeos-ai/beeos-types/runtime";import{TerminalNotFoundError}from"./session-manager.js";const MAX_OUTPUT_FRAME_BYTES=16384,OUTPUT_FLUSH_INTERVAL_MS=16;export class RuntimeTerminalService{manager;clients=new Map;clientSequences=new Map;openingFrames=new Map;pendingOutput=new Map;parked=new Map;outboundGeneration;constructor(e,n){this.manager=e,n&&this.setOutbound(n),e.addListeners({onOutput:e=>{const n=this.clients.get(e.terminalId);"service"===n?.kind&&n.clientId===e.sessionId&&n.incarnation===e.incarnation&&this.manager.isCurrentSession(e.terminalId,n.incarnation)&&this.queueOutput(e.terminalId,e.stream,e.data,e.seq,n)},onExit:e=>{const n=this.clients.get(e.terminalId);if("acp"===n?.kind&&n.clientId===e.sessionId&&n.incarnation===e.incarnation){const t=setTimeout(()=>{this.clients.get(e.terminalId)===n&&(this.clients.delete(e.terminalId),this.manager.releaseSessionIfCurrent(e.terminalId,n.incarnation))},0);return void t.unref?.()}"service"===n?.kind&&n.clientId===e.sessionId&&n.incarnation===e.incarnation&&this.manager.isCurrentSession(e.terminalId,n.incarnation)&&(this.flushPendingOutput(e.terminalId),this.emitFrame({protocolVersion:TERMINAL_SERVICE_PROTOCOL_VERSION,type:"terminal.exit",clientId:n.clientId,terminalId:e.terminalId,sequence:String(e.seq),...null===e.code?{}:{exitCode:e.code},...null===e.signal?{}:{signal:e.signal}},n))},onStateChange:e=>{if("closing"!==e.to)return;const n=this.clients.get(e.terminalId);if("acp"===n?.kind&&n.clientId===e.sessionId&&n.incarnation===e.incarnation){const t=setTimeout(()=>{this.clients.get(e.terminalId)!==n||this.manager.isCurrentSession(e.terminalId,e.incarnation)||this.clients.delete(e.terminalId)},0);return void t.unref?.()}"service"===n?.kind&&n.clientId===e.sessionId&&n.incarnation===e.incarnation&&this.isOwnedTerminal(n)&&n.generation.outbound.startTerminalReplayExpiry(n.clientId,e.terminalId)}})}setOutbound(e){if(this.outboundGeneration?.outbound===e)return this.outboundGeneration;if(this.outboundGeneration)throw new Error("Terminal outbound sink is already configured");const n=Object.freeze({outbound:e});return this.outboundGeneration=n,n}clearOutbound(e,n){const t=n??this.outboundGeneration;t&&t.outbound===e&&this.outboundGeneration===t&&(this.outboundGeneration=void 0,this.clearGenerationOwnership(t))}fenceServiceSessions(e="runtime_lease_lost",n=this.outboundGeneration){for(const[t,i]of this.clients)"service"!==i.kind||n&&i.generation!==n||this.manager.closeSessionIfCurrent(t,i.incarnation,e);if(n)this.clearGenerationOwnership(n);else{for(const[e,n]of this.clients)"service"===n.kind&&this.clients.delete(e);this.clientSequences.clear();for(const[e,n]of this.openingFrames)"service"===n.owner.kind&&this.openingFrames.delete(e)}}parkServiceSessions(e,n,t){for(const[i,r]of this.clients){if("service"!==r.kind||r.generation!==e)continue;if("running"!==this.manager.getSession(i)?.state){this.manager.closeSessionIfCurrent(i,r.incarnation,"runtime_lease_lost");continue}const s=setTimeout(()=>{this.parked.get(i)?.timer===s&&this.closeParked(i,"runtime_lease_lost")},Math.max(0,t));s.unref?.(),this.parked.set(i,{clientId:r.clientId,incarnation:r.incarnation,handlerIdentity:n,timer:s})}this.clearGenerationOwnership(e)}closeParkedServiceSessions(e,n){for(const[t,i]of this.parked)void 0!==n&&i.handlerIdentity===n||this.closeParked(t,e)}async handleClientFrame(e,n=this.outboundGeneration,t){if(!n||!this.isGenerationActive(n))throw new Error("terminal outbound generation is inactive");assertTerminalClientFrame(e);const i=BigInt(e.clientSequence),r=this.clientSequences.get(e.clientId);if(i<=(r?.generation===n?r.sequence:0n))throw new Error("non-monotonic terminal client sequence");if(this.clientSequences.set(e.clientId,{generation:n,sequence:i}),"terminal.open"===e.type||"terminal.create"===e.type){if(t?.recovered&&!this.clients.has(e.terminalId)&&!this.manager.getSession(e.terminalId))throw new TerminalNotFoundError(e.terminalId);if(this.clients.has(e.terminalId)||this.manager.getSession(e.terminalId)){const i=this.clients.get(e.terminalId);if(t?.recovered&&"service"===i?.kind&&i.clientId===e.clientId&&i.generation===n&&this.isOwnedTerminal(i)){const n=this.manager.getSession(e.terminalId);return void this.sendOrClose({protocolVersion:TERMINAL_SERVICE_PROTOCOL_VERSION,type:"terminal.opened",clientId:e.clientId,terminalId:e.terminalId,sequence:this.nextSequence(i),resumed:!0,columns:n.cols,rows:n.rows},i)}throw new Error("terminal already exists; use terminal.resume")}const i=this.manager.createSessionIncarnation(e.terminalId),r={kind:"service",clientId:e.clientId,generation:n,incarnation:i};this.clients.set(e.terminalId,r),this.openingFrames.set(e.terminalId,{owner:r,frames:[]});try{const n=this.manager.openSession({terminalId:e.terminalId,sessionId:e.clientId,command:e.command,args:e.args,env:e.env,cwd:e.cwd,cols:"terminal.open"===e.type?e.columns??80:80,rows:"terminal.open"===e.type?e.rows??24:24,outputLimitBytes:e.outputLimitBytes,initialServerSequence:2,incarnation:i});if(!this.isOwnedTerminal(r))return void this.manager.releaseSessionIfCurrent(e.terminalId,i);this.sendOrClose({protocolVersion:TERMINAL_SERVICE_PROTOCOL_VERSION,type:"terminal.opened",clientId:e.clientId,terminalId:n.terminalId,sequence:"1",resumed:!0===t?.recovered,columns:n.cols,rows:n.rows},r);const s=this.openingFrames.get(e.terminalId);s?.owner===r&&this.openingFrames.delete(e.terminalId);for(const e of s?.owner===r?s.frames:[])this.sendOrClose(e,r)}catch(n){throw this.clients.get(e.terminalId)===r&&this.clients.delete(e.terminalId),this.openingFrames.get(e.terminalId)?.owner===r&&this.openingFrames.delete(e.terminalId),this.manager.releaseSessionIfCurrent(e.terminalId,i),n}return}let s=!1;"terminal.resume"===e.type&&(s=this.adoptParkedForResume(e.clientId,e.terminalId,n,t),this.rebindServiceOwnerForResume(e.clientId,e.terminalId,n));const a=this.assertOwner(e.clientId,e.terminalId,n);switch(e.type){case"terminal.resume":{const t=n.outbound.resume(e.clientId,e.terminalId,e.lastServerSequence);if(!this.isOwnedTerminal(a))return;if((t.gap||s)&&this.sendOrClose({protocolVersion:TERMINAL_SERVICE_PROTOCOL_VERSION,type:"terminal.output",clientId:e.clientId,terminalId:e.terminalId,sequence:this.nextSequence(a),stream:"stdout",data:"",encoding:"base64",truncated:!0},a),!this.isOwnedTerminal(a))return;const i=this.manager.getSession(e.terminalId);return void this.sendOrClose({protocolVersion:TERMINAL_SERVICE_PROTOCOL_VERSION,type:"terminal.opened",clientId:e.clientId,terminalId:e.terminalId,sequence:this.nextSequence(a),resumed:!0,columns:i.cols,rows:i.rows},a)}case"terminal.input":return void this.manager.writeToSessionIfCurrent(e.terminalId,a.incarnation,Buffer.from(e.data,"base64"===e.encoding?"base64":"utf8"));case"terminal.resize":return void this.manager.resizeSessionIfCurrent(e.terminalId,a.incarnation,e.columns,e.rows);case"terminal.close":if(!this.manager.closeSessionIfCurrent(e.terminalId,a.incarnation,e.reason))throw new TerminalNotFoundError(e.terminalId);return;case"terminal.ping":if(this.manager.touchSessionIfCurrent(e.terminalId,a.incarnation),!this.isOwnedTerminal(a))return;return void this.sendOrClose({protocolVersion:TERMINAL_SERVICE_PROTOCOL_VERSION,type:"terminal.pong",clientId:e.clientId,terminalId:e.terminalId,sequence:this.nextSequence(a),nonce:e.nonce},a);case"terminal.wait":{let n;try{n=await this.manager.waitForExitIfCurrent(e.terminalId,a.incarnation,e.timeoutMs)}catch(e){if(!this.isOwnedTerminal(a))return;throw e}if(!this.isOwnedTerminal(a))return;return void this.sendOrClose({protocolVersion:TERMINAL_SERVICE_PROTOCOL_VERSION,type:"terminal.wait_result",clientId:e.clientId,terminalId:e.terminalId,sequence:this.nextSequence(a),exitCode:n.exitCode,output:n.output,state:n.state},a)}case"terminal.release":if(!this.manager.isCurrentSession(e.terminalId,a.incarnation))throw new TerminalNotFoundError(e.terminalId);const t=n.outbound.prepareForgetTerminal(e.clientId,e.terminalId);if(!this.manager.detachSessionIfCurrent(e.terminalId,a.incarnation))throw new TerminalNotFoundError(e.terminalId);return this.clients.get(e.terminalId)===a&&this.clients.delete(e.terminalId),this.dropPendingOutput(e.terminalId),void t();case"terminal.kill":if(!this.manager.killSessionIfCurrent(e.terminalId,a.incarnation,normalizeSignal(e.signal)))throw new TerminalNotFoundError(e.terminalId);return}}async execute(e,n){const t=this.outboundGeneration,i=t?.outbound;if(i?.isAvailable&&!i.isAvailable())throw new Error("terminal Service transport is unavailable; legacy fallback is forbidden");const r=e,s=r.context.terminal.terminalId,a=r.context.terminal.clientId;switch(r.name){case"terminal.open":{const e=r,n=this.manager.createSessionIncarnation(s),t=this.manager.openSession({terminalId:s,sessionId:e.context.terminal.clientId,command:e.params.command,args:e.params.args,env:e.params.env,cwd:e.params.cwd,cols:e.params.columns,rows:e.params.rows,outputLimitBytes:e.params.outputLimitBytes,incarnation:n});return this.clients.set(t.terminalId,{kind:"acp",clientId:e.context.terminal.clientId,incarnation:n}),{terminalId:t.terminalId,command:e.params.command??t.shell,shell:t.shell,cwd:t.cwd,columns:t.cols,rows:t.rows,state:t.state}}case"terminal.input":{const e=r,n=this.assertAcpOwner(a,s);return this.manager.writeToSessionIfCurrent(s,n.incarnation,Buffer.from(e.params.data,"base64"===e.params.encoding?"base64":"utf8")),{accepted:!0}}case"terminal.resize":{const e=r,n=this.assertAcpOwner(a,s);return this.manager.resizeSessionIfCurrent(s,n.incarnation,e.params.columns,e.params.rows),{resized:!0}}case"terminal.close":{const e=this.assertAcpOwner(a,s);if(!this.manager.closeSessionIfCurrent(s,e.incarnation,r.params.reason))throw new TerminalNotFoundError(s);return{closed:!0}}case"terminal.ping":{const e=this.assertAcpOwner(a,s);return this.manager.touchSessionIfCurrent(s,e.incarnation),{terminalId:s,status:"ok"}}case"terminal.create":{const e=r,n=this.manager.createSessionIncarnation(s),t=this.manager.openSession({terminalId:s,sessionId:e.context.terminal.clientId,command:e.params.command,args:e.params.args,env:e.params.env,cwd:e.params.cwd,cols:80,rows:24,outputLimitBytes:e.params.outputLimitBytes,incarnation:n});return this.clients.set(t.terminalId,{kind:"acp",clientId:e.context.terminal.clientId,incarnation:n}),{terminalId:t.terminalId,command:e.params.command}}case"terminal.wait":{const e=r,n=this.assertAcpOwner(a,s),t=await this.manager.waitForExitIfCurrent(s,n.incarnation,e.params.timeoutMs);if(!this.isOwnedAcpTerminal(n))throw new TerminalNotFoundError(s);return t}case"terminal.release":{const e=this.assertAcpOwner(a,s);if(!this.manager.detachSessionIfCurrent(s,e.incarnation))throw new TerminalNotFoundError(s);return this.clients.get(s)===e&&this.clients.delete(s),{released:!0}}case"terminal.kill":{const e=this.assertAcpOwner(a,s);if(!this.manager.killSessionIfCurrent(s,e.incarnation,normalizeSignal(r.params.signal)))throw new TerminalNotFoundError(s);return{killed:!0}}}throw new Error(`unsupported terminal command ${r.name}`)}nextSequence(e){return String(this.manager.nextServerSequence(e.incarnation.terminalId,e.incarnation))}sendOrClose(e,n){this.isOwnedTerminal(n)&&!n.generation.outbound.send(e)&&"terminal.output"!==e.type&&this.isOwnedTerminal(n)&&this.manager.closeSessionIfCurrent(e.terminalId,n.incarnation,"backpressure_limit")}emitFrame(e,n){if(!this.isOwnedTerminal(n))return;const t=this.openingFrames.get(e.terminalId);t?.owner===n?t.frames.push(e):this.sendOrClose(e,n)}assertOwner(e,n,t){const i=this.clients.get(n);if("service"!==i?.kind||i.clientId!==e||i.generation!==t||!this.isOwnedTerminal(i))throw new TerminalNotFoundError(n);return i}rebindServiceOwnerForResume(e,n,t){const i=this.clients.get(n);if("service"!==i?.kind||i.generation!==t||!this.isOwnedTerminal(i))return;if(i.clientId===e)return;if(this.flushPendingOutput(n),!this.manager.rebindSessionId(n,i.incarnation,e))throw new TerminalNotFoundError(n);t.outbound.rebindResumeClient(i.clientId,e,n),this.clientSequences.delete(i.clientId);const r={kind:"service",clientId:e,generation:i.generation,incarnation:i.incarnation};this.clients.set(n,r)}adoptParkedForResume(e,n,t,i){const r=this.parked.get(n);return!(!r||!0!==i?.recovered||this.clients.has(n))&&(clearTimeout(r.timer),this.parked.delete(n),"running"!==this.manager.getSession(n)?.state||r.clientId!==e&&!this.manager.rebindSessionId(n,r.incarnation,e)?(this.retireParked(n,r,"runtime_lease_lost"),!1):(this.clients.set(n,{kind:"service",clientId:e,generation:t,incarnation:r.incarnation}),!0))}closeParked(e,n){const t=this.parked.get(e);t&&(clearTimeout(t.timer),this.parked.delete(e),this.retireParked(e,t,n))}retireParked(e,n,t){"closed"===this.manager.getSession(e)?.state?this.manager.releaseSessionIfCurrent(e,n.incarnation):this.manager.closeSessionIfCurrent(e,n.incarnation,t)}assertAcpOwner(e,n){const t=this.clients.get(n);if("acp"!==t?.kind||t.clientId!==e||!this.isOwnedAcpTerminal(t))throw new TerminalNotFoundError(n);return t}isGenerationActive(e){return this.outboundGeneration===e}isOwnedTerminal(e){return this.outboundGeneration===e.generation&&this.clients.get(e.incarnation.terminalId)===e&&this.manager.isCurrentSession(e.incarnation.terminalId,e.incarnation)}isOwnedAcpTerminal(e){return this.clients.get(e.incarnation.terminalId)===e&&this.manager.isCurrentSession(e.incarnation.terminalId,e.incarnation)}expireServiceTerminalReplay(e,n,t){const i=this.clients.get(n);if("service"!==i?.kind||i.clientId!==e||i.generation!==t||!this.isOwnedTerminal(i))return;const r=t.outbound.prepareForgetTerminal(e,n);if(!("closed"===this.manager.getSession(n)?.state?this.manager.releaseSessionIfCurrent(n,i.incarnation):this.manager.detachSessionIfCurrent(n,i.incarnation)))throw new TerminalNotFoundError(n);this.clients.get(n)===i&&this.clients.delete(n),this.openingFrames.get(n)?.owner===i&&this.openingFrames.delete(n),this.dropPendingOutput(n),r()}clearGenerationOwnership(e){for(const[n,t]of this.clients)"service"===t.kind&&t.generation===e&&this.clients.delete(n);for(const[n,t]of this.clientSequences)t.generation===e&&this.clientSequences.delete(n);for(const[n,t]of this.openingFrames)t.owner.generation===e&&this.openingFrames.delete(n);for(const[n,t]of this.pendingOutput)t.owner.generation===e&&this.dropPendingOutput(n)}queueOutput(e,n,t,i,r){let s=0,a=i;for(;s<t.length;){let i=this.pendingOutput.get(e);!i||i.owner===r&&i.stream===n||(this.flushPendingOutput(e),i=void 0),i||(i={owner:r,stream:n,chunks:[],bytes:0,sequence:a},this.pendingOutput.set(e,i));const o=Math.min(16384-i.bytes,t.length-s);i.chunks.push(t.subarray(s,s+o)),i.bytes+=o,i.sequence=a,s+=o,16384!==i.bytes?i.timer||(i.timer=setTimeout(()=>{this.pendingOutput.get(e)===i&&this.flushPendingOutput(e)},16),i.timer.unref?.()):(this.flushPendingOutput(e),s<t.length&&(a=this.manager.nextServerSequence(e,r.incarnation)))}}flushPendingOutput(e){const n=this.pendingOutput.get(e);if(!n)return;if(this.pendingOutput.delete(e),n.timer&&clearTimeout(n.timer),0===n.bytes||!this.isOwnedTerminal(n.owner))return;const t=1===n.chunks.length?n.chunks[0]:Buffer.concat(n.chunks,n.bytes);this.emitFrame({protocolVersion:TERMINAL_SERVICE_PROTOCOL_VERSION,type:"terminal.output",clientId:n.owner.clientId,terminalId:e,sequence:String(n.sequence),stream:n.stream,data:t.toString("base64"),encoding:"base64"},n.owner)}dropPendingOutput(e){const n=this.pendingOutput.get(e);n&&(n.timer&&clearTimeout(n.timer),this.pendingOutput.delete(e))}}const runtimeServices=new WeakMap;export function getOrCreateRuntimeTerminalService(e){let n=runtimeServices.get(e);return n||(n=new RuntimeTerminalService(e),runtimeServices.set(e,n)),n}export function assertTerminalClientFrame(e){const n=e;if(!(n&&CLIENT_FRAME_TYPES.has(String(n.type))&&n.protocolVersion===TERMINAL_SERVICE_PROTOCOL_VERSION&&isUuid(n.clientId)&&isUuid(n.terminalId)&&isPositiveRuntimeSequence(n.clientSequence)))throw new Error("invalid terminal client frame");switch(e.type){case"terminal.open":assertOptionalCommand(e.command),assertOptionalArgs(e.args),assertOptionalEnv(e.env),assertOptionalString(e.cwd),assertOptionalDimension(e.columns),assertOptionalDimension(e.rows),assertOptionalOutputLimit(e.outputLimitBytes);break;case"terminal.create":("string"!=typeof e.command||e.command.length<1||e.command.length>4096)&&invalidFrame(),assertOptionalArgs(e.args),assertOptionalEnv(e.env),assertOptionalString(e.cwd),assertOptionalOutputLimit(e.outputLimitBytes);break;case"terminal.resume":isRuntimeSequence(e.lastServerSequence)||invalidFrame();break;case"terminal.input":("string"!=typeof e.data||"utf8"!==e.encoding&&"base64"!==e.encoding)&&invalidFrame();break;case"terminal.resize":assertDimension(e.columns),assertDimension(e.rows);break;case"terminal.close":assertOptionalString(e.reason);break;case"terminal.ping":"string"!=typeof e.nonce&&invalidFrame();break;case"terminal.wait":void 0!==e.timeoutMs&&(!Number.isInteger(e.timeoutMs)||e.timeoutMs<0||e.timeoutMs>864e5)&&invalidFrame();break;case"terminal.kill":assertOptionalString(e.signal)}}const CLIENT_FRAME_TYPES=new Set(["terminal.open","terminal.resume","terminal.input","terminal.resize","terminal.close","terminal.ping","terminal.create","terminal.wait","terminal.release","terminal.kill"]);function normalizeSignal(e){return e&&e.startsWith("SIG")?e:"SIGTERM"}const UUID_PATTERN=/^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i,UINT64_MAX=18446744073709551615n;function isUuid(e){return"string"==typeof e&&UUID_PATTERN.test(e)}function isRuntimeSequence(e){return"string"==typeof e&&/^(0|[1-9][0-9]*)$/.test(e)&&BigInt(e)<=UINT64_MAX}function isPositiveRuntimeSequence(e){return isRuntimeSequence(e)&&"0"!==e}function invalidFrame(){throw new Error("invalid terminal client frame")}function assertOptionalString(e){void 0!==e&&"string"!=typeof e&&invalidFrame()}function assertOptionalCommand(e){void 0!==e&&("string"!=typeof e||e.length<1||e.length>4096)&&invalidFrame()}function assertOptionalArgs(e){void 0===e||Array.isArray(e)&&e.every(e=>"string"==typeof e)||invalidFrame()}function assertOptionalEnv(e){void 0===e||e&&"object"==typeof e&&!Array.isArray(e)&&Object.values(e).every(e=>"string"==typeof e)||invalidFrame()}function assertDimension(e){(!Number.isInteger(e)||e<1||e>1e3)&&invalidFrame()}function assertOptionalDimension(e){void 0!==e&&assertDimension(e)}function assertOptionalOutputLimit(e){void 0!==e&&(!Number.isInteger(e)||e<1||e>16777216)&&invalidFrame()}
@@ -1 +1 @@
1
- import{createHash,randomUUID}from"node:crypto";import{EventEmitter}from"node:events";import WebSocket from"ws";import{signAgentMessage}from"../agent-auth.js";export function terminalAgentAuthSigningMessage(e){return`terminal|${e.public_key}|${e.timestamp}|${e.nonce}|${e.runtimeLeaseCredential}|${e.handlerIdentity}|${e.runtimeEpoch}|${e.leaseId}`}export function createTerminalLeaseBoundAgentAuth(e){if(!(32===e.keys.publicKey.length&&[32,48].includes(e.keys.privateKey.length)&&e.lease.instanceId&&e.lease.leaseId&&e.lease.handlerIdentity&&e.lease.runtimeLeaseCredential&&isPositiveRuntimeEpoch(e.lease.runtimeEpoch)&&Number.isSafeInteger(e.timestamp)&&!(e.timestamp<0)&&e.nonce))throw new Error("invalid Terminal runtime lease authority binding");const t={service:"terminal",public_key:Buffer.from(e.keys.publicKey).toString("base64"),timestamp:e.timestamp,nonce:e.nonce,runtimeLeaseCredential:e.lease.runtimeLeaseCredential,handlerIdentity:e.lease.handlerIdentity,runtimeEpoch:e.lease.runtimeEpoch,leaseId:e.lease.leaseId};return{type:"agent_auth",instance_id:e.lease.instanceId,...t,signature:Buffer.from(signAgentMessage(terminalAgentAuthSigningMessage(t),e.keys.privateKey)).toString("base64")}}export class TerminalLeaseBoundBridgeClient extends EventEmitter{options;state="idle";socket;unsubscribe;expiryTimer;reconnectTimer;generation=0;stopped=!0;authenticatedBindingHash;authorityEpochBindingHash;transition=Promise.resolve();constructor(e){super(),this.options=e}async connect(){this.unsubscribe||(this.unsubscribe=this.options.authority.subscribeAuthority(e=>this.onAuthorityChanged(e))),this.stopped=!1;const e=this.options.authority.authorityLease;if(!e)throw new Error("active runtime registration lease required for Terminal auth");await this.queueTransition(()=>this.replaceConnection(e))}async send(e){const t=this.socket;if("connected"!==this.state||!t||t.readyState!==WebSocket.OPEN)throw new Error("Terminal lease-bound Bridge is not connected");await new Promise((i,n)=>t.send(e,e=>e?n(e):i()))}async close(e=!0){this.stopped=!0,this.clearTimers(),this.unsubscribe?.(),this.unsubscribe=void 0,this.authenticatedBindingHash=void 0,this.authorityEpochBindingHash=void 0,this.generation++,await this.closeSocket(e),this.state="closed"}onAuthorityChanged(e){if(this.stopped)return;if(!e)return void this.fenceAuthority();this.scheduleExpiryFence(e);const t=authorityEpochHash(e),i=authorityBindingHash(e);return this.authorityEpochBindingHash&&t!==this.authorityEpochBindingHash?(this.authorityEpochBindingHash=t,this.emit("transportEpochChanged"),void this.queueTransition(()=>this.replaceConnection(e)).catch(e=>this.emitError(e))):i!==this.authenticatedBindingHash?"connected"===this.state&&this.authenticatedBindingHash?(this.authenticatedBindingHash=i,void(this.authorityEpochBindingHash??=t)):void this.queueTransition(()=>this.replaceConnection(e)).catch(e=>this.emitError(e)):void 0}async replaceConnection(e){if(this.stopped||null===this.options.authority.authorityLease)return;this.clearReconnectTimer();const t=++this.generation;if(await this.closeSocket(!1),this.stopped||t!==this.generation)return;this.state="connecting";const i=new WebSocket(`${this.options.bridgeUrl.replace(/\/+$/,"")}/agent`);this.socket=i;const n=authorityBindingHash(e);await new Promise((s,o)=>{let r=!1;const a=setTimeout(()=>{c(new Error("Terminal auth timeout")),i.readyState!==WebSocket.CLOSED&&i.terminate()},this.options.authTimeoutMs??1e4),h=()=>{clearTimeout(a),i.off("open",u)},c=e=>{h(),r||o(e)},u=()=>{const e=this.options.authority.authorityLease;if(!e||authorityBindingHash(e)!==n||t!==this.generation)return c(new Error("Terminal runtime lease changed during auth")),void i.close();const s=createTerminalLeaseBoundAgentAuth({lease:e,keys:this.options.keys,timestamp:Math.floor((this.options.now??Date.now)()/1e3),nonce:(this.options.nonce??randomUUID)()});i.send(JSON.stringify(s),e=>{e&&c(e)})};i.on("open",u),i.on("message",o=>{if(!r){try{if("auth_ok"!==JSON.parse(o.toString()).type)return}catch{return}return r=!0,h(),t!==this.generation?void i.close():(this.authenticatedBindingHash=n,this.authorityEpochBindingHash=authorityEpochHash(e),this.state="connected",this.scheduleExpiryFence(e),this.emit("connected"),void s())}this.emit("message",o)}),i.on("error",e=>{this.emitError(e),c(e)}),i.on("close",(e,i)=>{h(),r||o(new Error(`Terminal auth rejected: ${i.toString()||"connection closed"}`)),t===this.generation&&(this.socket=void 0,this.authenticatedBindingHash=void 0,this.state="idle",this.emit("disconnected",i.toString()||"connection closed"),1008!==e?!this.stopped&&this.options.authority.authorityLease&&this.scheduleReconnect():this.fenceAuthority())})})}fenceAuthority(){this.clearTimers(),this.authenticatedBindingHash=void 0,this.authorityEpochBindingHash=void 0,this.generation++;const e=this.socket;this.socket=void 0,this.state="idle",e&&e.readyState!==WebSocket.CLOSED&&e.terminate(),this.emit("authorityLost")}scheduleExpiryFence(e){this.expiryTimer&&clearTimeout(this.expiryTimer);const t=authorityBindingHash(e);this.expiryTimer=setTimeout(()=>{const e=this.options.authority.authorityLease;e?authorityBindingHash(e)===t&&(Date.parse(e.leaseExpiresAt)>(this.options.now??Date.now)()?this.scheduleExpiryFence(e):this.fenceAuthority()):this.fenceAuthority()},Math.min(2147483647,Math.max(0,Date.parse(e.leaseExpiresAt)-(this.options.now??Date.now)()))),this.expiryTimer.unref?.()}scheduleReconnect(){this.reconnectTimer||this.stopped||(this.reconnectTimer=setTimeout(()=>{this.reconnectTimer=void 0;const e=this.options.authority.authorityLease;e&&this.queueTransition(()=>this.replaceConnection(e)).catch(e=>{this.emitError(e),this.scheduleReconnect()})},this.options.reconnectDelayMs??1e3),this.reconnectTimer.unref?.())}async closeSocket(e){const t=this.socket;this.socket=void 0,t&&t.readyState!==WebSocket.CLOSED&&(e&&t.readyState===WebSocket.OPEN?await new Promise(e=>{t.once("close",()=>e()),t.close()}):t.terminate())}queueTransition(e){const t=this.transition.then(e,e);return this.transition=t.catch(()=>{}),t}clearReconnectTimer(){this.reconnectTimer&&clearTimeout(this.reconnectTimer),this.reconnectTimer=void 0}clearTimers(){this.clearReconnectTimer(),this.expiryTimer&&clearTimeout(this.expiryTimer),this.expiryTimer=void 0}emitError(e){this.listenerCount("error")>0&&this.emit("error",e instanceof Error?e:new Error(String(e)))}}function authorityEpochHash(e){return createHash("sha256").update(`${e.instanceId}\0${e.leaseId}\0${e.handlerIdentity}\0${e.runtimeEpoch}`).digest("hex")}function authorityBindingHash(e){return createHash("sha256").update(`${e.instanceId}\0${e.leaseId}\0${e.handlerIdentity}\0${e.runtimeEpoch}\0${e.runtimeLeaseCredential}\0${e.leaseExpiresAt}`).digest("hex")}function isPositiveRuntimeEpoch(e){return"string"==typeof e&&/^[1-9][0-9]*$/.test(e)&&BigInt(e)<=18446744073709551615n}
1
+ import{createHash,randomUUID}from"node:crypto";import{EventEmitter}from"node:events";import WebSocket from"ws";import{signAgentMessage}from"../agent-auth.js";export function terminalAgentAuthSigningMessage(e){return`terminal|${e.public_key}|${e.timestamp}|${e.nonce}|${e.runtimeLeaseCredential}|${e.handlerIdentity}|${e.runtimeEpoch}|${e.leaseId}`}export function createTerminalLeaseBoundAgentAuth(e){if(!(32===e.keys.publicKey.length&&[32,48].includes(e.keys.privateKey.length)&&e.lease.instanceId&&e.lease.leaseId&&e.lease.handlerIdentity&&e.lease.runtimeLeaseCredential&&isPositiveRuntimeEpoch(e.lease.runtimeEpoch)&&Number.isSafeInteger(e.timestamp)&&!(e.timestamp<0)&&e.nonce))throw new Error("invalid Terminal runtime lease authority binding");const t={service:"terminal",public_key:Buffer.from(e.keys.publicKey).toString("base64"),timestamp:e.timestamp,nonce:e.nonce,runtimeLeaseCredential:e.lease.runtimeLeaseCredential,handlerIdentity:e.lease.handlerIdentity,runtimeEpoch:e.lease.runtimeEpoch,leaseId:e.lease.leaseId};return{type:"agent_auth",instance_id:e.lease.instanceId,...t,signature:Buffer.from(signAgentMessage(terminalAgentAuthSigningMessage(t),e.keys.privateKey)).toString("base64")}}export class TerminalLeaseBoundBridgeClient extends EventEmitter{options;state="idle";socket;unsubscribe;expiryTimer;reconnectTimer;generation=0;stopped=!0;authenticatedBindingHash;authorityEpochBindingHash;transition=Promise.resolve();constructor(e){super(),this.options=e}async connect(){this.unsubscribe||(this.unsubscribe=this.options.authority.subscribeAuthority((e,t)=>this.onAuthorityChanged(e,t))),this.stopped=!1;const e=this.options.authority.authorityLease;if(!e)throw new Error("active runtime registration lease required for Terminal auth");await this.queueTransition(()=>this.replaceConnection(e))}async send(e){const t=this.socket;if("connected"!==this.state||!t||t.readyState!==WebSocket.OPEN)throw new Error("Terminal lease-bound Bridge is not connected");await new Promise((i,n)=>t.send(e,e=>e?n(e):i()))}async close(e=!0){this.stopped=!0,this.clearTimers(),this.unsubscribe?.(),this.unsubscribe=void 0,this.authenticatedBindingHash=void 0,this.authorityEpochBindingHash=void 0,this.generation++,await this.closeSocket(e),this.state="closed"}onAuthorityChanged(e,t){if(this.stopped)return;if(!e)return void this.fenceAuthority(void 0===t);this.scheduleExpiryFence(e);const i=authorityEpochHash(e),n=authorityBindingHash(e);return this.authorityEpochBindingHash&&i!==this.authorityEpochBindingHash?(this.authorityEpochBindingHash=i,this.emit("transportEpochChanged"),void this.queueTransition(()=>this.replaceConnection(e)).catch(e=>this.emitError(e))):n!==this.authenticatedBindingHash?"connected"===this.state&&this.authenticatedBindingHash?(this.authenticatedBindingHash=n,void(this.authorityEpochBindingHash??=i)):void this.queueTransition(()=>this.replaceConnection(e)).catch(e=>this.emitError(e)):void 0}async replaceConnection(e){if(this.stopped||null===this.options.authority.authorityLease)return;this.clearReconnectTimer();const t=++this.generation;if(await this.closeSocket(!1),this.stopped||t!==this.generation)return;this.state="connecting";const i=new WebSocket(`${this.options.bridgeUrl.replace(/\/+$/,"")}/agent`);this.socket=i;const n=authorityBindingHash(e);await new Promise((s,o)=>{let r=!1;const a=setTimeout(()=>{c(new Error("Terminal auth timeout")),i.readyState!==WebSocket.CLOSED&&i.terminate()},this.options.authTimeoutMs??1e4),h=()=>{clearTimeout(a),i.off("open",u)},c=e=>{h(),r||o(e)},u=()=>{const e=this.options.authority.authorityLease;if(!e||authorityBindingHash(e)!==n||t!==this.generation)return c(new Error("Terminal runtime lease changed during auth")),void i.close();const s=createTerminalLeaseBoundAgentAuth({lease:e,keys:this.options.keys,timestamp:Math.floor((this.options.now??Date.now)()/1e3),nonce:(this.options.nonce??randomUUID)()});i.send(JSON.stringify(s),e=>{e&&c(e)})};i.on("open",u),i.on("message",o=>{if(!r){try{if("auth_ok"!==JSON.parse(o.toString()).type)return}catch{return}return r=!0,h(),t!==this.generation?void i.close():(this.authenticatedBindingHash=n,this.authorityEpochBindingHash=authorityEpochHash(e),this.state="connected",this.scheduleExpiryFence(e),this.emit("connected",{handlerIdentity:e.handlerIdentity}),void s())}this.emit("message",o)}),i.on("error",e=>{this.emitError(e),c(e)}),i.on("close",(e,i)=>{h(),r||o(new Error(`Terminal auth rejected: ${i.toString()||"connection closed"}`)),t===this.generation&&(this.socket=void 0,this.authenticatedBindingHash=void 0,this.state="idle",this.emit("disconnected",i.toString()||"connection closed"),1008!==e?!this.stopped&&this.options.authority.authorityLease&&this.scheduleReconnect():this.fenceAuthority(!1))})})}fenceAuthority(e){this.clearTimers(),this.authenticatedBindingHash=void 0,this.authorityEpochBindingHash=void 0,this.generation++;const t=this.socket;this.socket=void 0,this.state="idle",t&&t.readyState!==WebSocket.CLOSED&&t.terminate(),this.emit("authorityLost",{recoverable:e})}scheduleExpiryFence(e){this.expiryTimer&&clearTimeout(this.expiryTimer);const t=authorityBindingHash(e);this.expiryTimer=setTimeout(()=>{const e=this.options.authority.authorityLease;e?authorityBindingHash(e)===t&&(Date.parse(e.leaseExpiresAt)>(this.options.now??Date.now)()?this.scheduleExpiryFence(e):this.fenceAuthority(!0)):this.fenceAuthority(!0)},Math.min(2147483647,Math.max(0,Date.parse(e.leaseExpiresAt)-(this.options.now??Date.now)()))),this.expiryTimer.unref?.()}scheduleReconnect(){this.reconnectTimer||this.stopped||(this.reconnectTimer=setTimeout(()=>{this.reconnectTimer=void 0;const e=this.options.authority.authorityLease;e&&this.queueTransition(()=>this.replaceConnection(e)).catch(e=>{this.emitError(e),this.scheduleReconnect()})},this.options.reconnectDelayMs??1e3),this.reconnectTimer.unref?.())}async closeSocket(e){const t=this.socket;this.socket=void 0,t&&t.readyState!==WebSocket.CLOSED&&(e&&t.readyState===WebSocket.OPEN?await new Promise(e=>{t.once("close",()=>e()),t.close()}):t.terminate())}queueTransition(e){const t=this.transition.then(e,e);return this.transition=t.catch(()=>{}),t}clearReconnectTimer(){this.reconnectTimer&&clearTimeout(this.reconnectTimer),this.reconnectTimer=void 0}clearTimers(){this.clearReconnectTimer(),this.expiryTimer&&clearTimeout(this.expiryTimer),this.expiryTimer=void 0}emitError(e){this.listenerCount("error")>0&&this.emit("error",e instanceof Error?e:new Error(String(e)))}}function authorityEpochHash(e){return createHash("sha256").update(`${e.instanceId}\0${e.leaseId}\0${e.handlerIdentity}\0${e.runtimeEpoch}`).digest("hex")}function authorityBindingHash(e){return createHash("sha256").update(`${e.instanceId}\0${e.leaseId}\0${e.handlerIdentity}\0${e.runtimeEpoch}\0${e.runtimeLeaseCredential}\0${e.leaseExpiresAt}`).digest("hex")}function isPositiveRuntimeEpoch(e){return"string"==typeof e&&/^[1-9][0-9]*$/.test(e)&&BigInt(e)<=18446744073709551615n}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "beeos-claw",
3
- "version": "0.6.66-cloud.0",
3
+ "version": "0.6.66-cloud.2",
4
4
  "description": "OpenClaw plugin that bridges the BeeOS platform with the local OpenClaw Gateway, providing file transfer, ACP bridge, and observability.",
5
5
  "license": "MIT",
6
6
  "author": "BeeOS <dev@beeos.ai>",
@@ -58,16 +58,17 @@
58
58
  "build": "npm run clean && tsc -p tsconfig.build.json",
59
59
  "build:release": "npm run build && npm run minify",
60
60
  "typecheck": "tsc -p tsconfig.json --noEmit",
61
- "lint": "npm run typecheck && eslint \"src/**/*.ts\"",
61
+ "lint": "npm run question:verify && npm run typecheck && eslint \"src/**/*.ts\"",
62
62
  "minify": "find dist -name '*.js' -exec sh -c 'for file do terser \"$file\" --compress drop_console=true,drop_debugger=true --mangle --output \"$file.min\" && mv \"$file.min\" \"$file\" || exit 1; done' sh {} + && find dist -name '*.d.ts' -delete && find dist -name '*.d.ts.map' -delete",
63
63
  "test": "npm run test:ci",
64
64
  "test:ci": "node ./scripts/test-ci.mjs",
65
- "test:watch": "vitest"
65
+ "test:watch": "vitest",
66
+ "question:verify": "node src/contracts/question/verify.mjs"
66
67
  },
67
68
  "dependencies": {
68
- "@beeos-ai/beeos-types": "4.4.1",
69
+ "@beeos-ai/beeos-types": "4.4.2-thinking.0",
69
70
  "@beeos-ai/bridge-client": "^0.1.0",
70
- "@beeos-ai/message-sdk": "2.0.16",
71
+ "@beeos-ai/message-sdk": "2.0.18-question.0",
71
72
  "@modelcontextprotocol/sdk": "^1.32.1",
72
73
  "adm-zip": "0.6.1",
73
74
  "ws": "^8.18.0"
@@ -87,5 +88,5 @@
87
88
  "fast-uri": ">=4.2.1",
88
89
  "brace-expansion": ">=5.0.12"
89
90
  },
90
- "gitHead": "e159928af6076bb633018e93bc1e5a7a085182b8"
91
+ "gitHead": "3edee494122bddd7a806ae43b840c50dfa5ea327"
91
92
  }
@@ -4,32 +4,32 @@
4
4
  "hashAlgorithm": "SHA-256",
5
5
  "canonicalTypes": {
6
6
  "package": "@beeos-ai/beeos-types",
7
- "version": "4.4.1",
7
+ "version": "4.4.2-thinking.0",
8
8
  "runtimeRpcContractRevision": "2026-07-14.3",
9
- "runtimeMethodManifestRawSHA256": "2af68ff555d4a4aa3c7b19a285e48a3df2ef9b43aa618ffddabbebc6e4280162",
10
- "runtimeMethodManifestJcsSHA256": "d5db2c5e7c78cee73e5672a3c18a8e56179b28888f6fbb660d98ec021ca9de9b"
9
+ "runtimeMethodManifestRawSHA256": "dd0eca4c8229ef26b788a86b39dfd2b05ab1a096d94b53ae355511f4c99756ed",
10
+ "runtimeMethodManifestJcsSHA256": "de658b3a2ac506d1199c8972dc081f35ead75e601a2bf36dd567ba7634d883ac"
11
11
  },
12
12
  "artifacts": [
13
13
  {
14
14
  "name": "runtime-parity-manifest",
15
15
  "path": "openapi/runtime-parity-manifest-v1.json",
16
- "revision": "beeos.runtime-parity.v1.1.0",
17
- "rawSHA256": "7028b5a0dd85ee78d7df0690d2ef13ef2118acb08a1c8f718d3e9f08da8f8404",
18
- "jcsSHA256": "d464594fe15a1657c805c8829d676a4c93aec5c43e020f3b1fae21f4c9453ca7"
16
+ "revision": "beeos.runtime-parity.v1.2.0",
17
+ "rawSHA256": "a88335bd662d3ef94c24aa01f0174e6917524766c6c6cac3695c7282a239ff7d",
18
+ "jcsSHA256": "a49592ac3be446dc20537cf1d7fe3b0c517a9ccb5e6aca8b69b421f777066f52"
19
19
  },
20
20
  {
21
21
  "name": "runtime-message-channel",
22
22
  "path": "openapi/runtime-message-channel-v1.contract.json",
23
- "revision": "beeos.runtime-message-channel.v2.0.0",
24
- "rawSHA256": "dd791ce5f49b7bee1dba21a71e7b2cfbf4db7d77cb787aab5f04c7f4b0e3101b",
25
- "jcsSHA256": "e950f1a86bf2bd8bf472603a91111c15b920fcac4c06fd8ac70e2c3f4c3a408f"
23
+ "revision": "beeos.runtime-message-channel.v2.1.0",
24
+ "rawSHA256": "79e66c441fac7f1bd493f837a4814b1c1d82fb0110863d99bcc8d85fe040d5e9",
25
+ "jcsSHA256": "cec634cca225629056b7bff9605008ee975249da11e493f0c54da85c848635b2"
26
26
  },
27
27
  {
28
28
  "name": "runtime-authority-ports",
29
29
  "path": "openapi/runtime-authority-ports-v1.contract.json",
30
- "revision": "beeos.runtime-authority-ports.v2.0.0",
31
- "rawSHA256": "a2e9565ae617bcaeb4d837fe176a3602be041913d28a3f0a3a9ec2e3dae0d7b4",
32
- "jcsSHA256": "3bd582f1f6babcdbe953a082faf813247c960f6bbf4c0f713e7fd92e27d163f5"
30
+ "revision": "beeos.runtime-authority-ports.v2.1.0",
31
+ "rawSHA256": "42b99ef67bb889375ede81803d3800c47a52ce5ea10faffa1ccfb7d3062afcde",
32
+ "jcsSHA256": "8ebee5df8bd325da15bc47e75db444eb25390d61285f579bb54e31f114f70e36"
33
33
  }
34
34
  ]
35
35
  }