beeos-claw 0.2.6 → 0.2.7
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.
|
@@ -1 +1 @@
|
|
|
1
|
-
import{spawn}from"node:child_process";import{createRequire}from"node:module";import{randomUUID}from"node:crypto";import{realpathSync}from"node:fs";import{dirname,resolve}from"node:path";export const TERMINAL_TIMEOUT_REASONS={idle:"idle_timeout",maxDuration:"max_duration_timeout"};const ALLOWED_STATE_TRANSITIONS={opening:["running","closing","closed"],running:["closing","closed"],closing:["closed"],closed:[]},require=createRequire(import.meta.url);let cachedNodePty;function resolveOpenclawNodeModulesDir(){const e=process.argv[1];if(e)try{const t=realpathSync(e);if(/openclaw/i.test(t))return dirname(t)}catch{}const t=["/opt/homebrew/lib/node_modules/openclaw","/usr/local/lib/node_modules/openclaw","/usr/lib/node_modules/openclaw"];for(const e of t)try{return realpathSync(resolve(e,"package.json")),e}catch{}return null}function getNodePty(){if(void 0!==cachedNodePty)return cachedNodePty;const e=["node-pty","@lydell/node-pty"];for(const t of e)try{return cachedNodePty=require(t),cachedNodePty}catch{}const t=resolveOpenclawNodeModulesDir();if(t){const s=createRequire(resolve(t,"package.json"));for(const t of e)try{return cachedNodePty=s(t),cachedNodePty}catch{}}return cachedNodePty=null,null}function createPtyProcess(e){const t=getNodePty();if(!t)throw new Error("node-pty not available");const s={...process.env,TERM:"xterm-256color"},i=t.spawn(e.shell,[],{name:"xterm-256color",cwd:e.cwd,env:s,cols:e.cols,rows:e.rows});let o=!1;return i.onData(t=>{e.onData(Buffer.from(t,"utf8"))}),i.onExit(t=>{var s;s={code:Number.isFinite(t.exitCode)?t.exitCode:null,signal:null!=t.signal?String(t.signal):null},o||(o=!0,e.onExit(s))}),{write(e){const t=e.toString("utf8");t&&i.write(t)},resize(e,t){i.resize(e,t)},close(){try{i.kill()}catch{}}}}function createPipeProcess(e){const t=spawn(e.shell,[],{shell:!0,cwd:e.cwd,stdio:["pipe","pipe","pipe"],env:{...process.env,TERM:"xterm-256color"}});let s=!1;const i=t=>{s||(s=!0,e.onExit(t))};return t.stdout?.on("data",t=>e.onData(Buffer.isBuffer(t)?t:Buffer.from(t))),t.stderr?.on("data",t=>e.onData(Buffer.isBuffer(t)?t:Buffer.from(t))),t.once("error",e=>i({code:null,signal:null})),t.once("exit",(e,t)=>i({code:e??null,signal:t??null})),{write(e){t.stdin?.destroyed||t.stdin?.write(e)},resize(e,t){},close(){t.killed||t.kill()}}}function createDefaultProcess(e){try{return createPtyProcess(e)}catch{return createPipeProcess(e)}}const DEFAULT_MAX_CONCURRENT=10,DEFAULT_IDLE_TIMEOUT_MS=432e5,DEFAULT_MAX_DURATION_MS=432e5;export class TerminalNotFoundError extends Error{terminalId;constructor(e){super(`terminal not found: ${e}`),this.name="TerminalNotFoundError",this.terminalId=e}}export class TerminalClosedError extends Error{terminalId;state;constructor(e,t){super(`terminal is not writable in state=${t}: ${e}`),this.name="TerminalClosedError",this.terminalId=e,this.state=t}}function chainHandler(e,t){return t?e?s=>{e(s),t(s)}:t:e}export class TerminalManager{sessions=new Map;maxConcurrent;idleTimeoutMs;maxDurationMs;onOutput;onExit;onStateChange;constructor(e){this.maxConcurrent=e?.maxConcurrent??10,this.idleTimeoutMs=e?.idleTimeoutMs??432e5,this.maxDurationMs=e?.maxDurationMs??432e5}addListeners(e){this.onOutput=chainHandler(this.onOutput,e.onOutput),this.onExit=chainHandler(this.onExit,e.onExit),this.onStateChange=chainHandler(this.onStateChange,e.onStateChange)}openSession(e){if(this.sessions.size>=this.maxConcurrent&&this.evictOldest(),this.sessions.size>=this.maxConcurrent)throw new Error("terminal quota exceeded");const t=e.terminalId&&!this.sessions.has(e.terminalId)?e.terminalId:`term_${randomUUID().slice(0,12)}`,s=e.sessionId??t,i=e.command||process.env.SHELL||"/bin/bash",o=e.cwd||process.cwd(),n=Date.now(),r={terminalId:t,sessionId:s,shell:i,cwd:o,startedAt:n,lastActiveAt:n,cols:e.cols,rows:e.rows,state:"opening",nextSeq:1,output:[],process:null};this.sessions.set(t,r);try{r.process=createDefaultProcess({shell:i,cwd:o,cols:e.cols,rows:e.rows,onData:e=>this.handleProcessData(t,e),onExit:e=>this.handleProcessExit(t,e)}),this.transitionState(r,"running"),this.scheduleIdleTimeout(r),this.scheduleMaxDuration(r)}catch(e){throw this.clearTimers(r),this.transitionState(r,"closed"),this.sessions.delete(t),e}return this.snapshot(r)}writeToSession(e,t){const s=this.requireSession(e);this.assertRunning(s),s.process?.write(t),this.markActivity(s)}resizeSession(e,t,s){const i=this.requireSession(e);this.assertRunning(i),i.cols=t,i.rows=s,i.process?.resize(t,s),this.markActivity(i)}closeSession(e,t){const s=this.sessions.get(e);s&&(t&&!s.timeoutReason&&(s.timeoutReason=t),"closed"!==s.state?(this.clearTimers(s),"closing"!==s.state&&(this.transitionState(s,"closing"),s.process?.close())):this.clearTimers(s))}touchSession(e){const t=this.requireSession(e);this.assertRunning(t),this.markActivity(t)}getSession(e){const t=this.sessions.get(e);return t?this.snapshot(t):void 0}create(e){const t=this.openSession({command:e,cols:80,rows:24}),s=this.sessions.get(t.terminalId);return{id:s.terminalId,command:s.shell,state:s.state,output:s.output,exitCode:s.exitCode,seq:s.nextSeq-1}}get(e){const t=this.sessions.get(e);if(t)return{id:t.terminalId,state:t.state,output:t.output,exitCode:t.exitCode,seq:t.nextSeq-1}}write(e,t){try{return this.writeToSession(e,Buffer.from(t,"utf8")),!0}catch{return!1}}resize(e,t,s){try{return this.resizeSession(e,t,s),!0}catch{return!1}}kill(e,t="SIGTERM"){const s=this.sessions.get(e);s&&s.process&&("closing"!==s.state&&this.transitionState(s,"closing"),s.process.close())}release(e){const t=this.sessions.get(e);t&&(!t.process||"running"!==t.state&&"opening"!==t.state||this.kill(e),this.clearTimers(t),this.sessions.delete(e))}listSessions(){return[...this.sessions.values()].map(e=>this.snapshot(e))}get sessionCount(){return this.sessions.size}get maxConcurrentLimit(){return this.maxConcurrent}destroy(){for(const e of[...this.sessions.keys()])this.release(e)}snapshot(e){return{terminalId:e.terminalId,sessionId:e.sessionId,shell:e.shell,cwd:e.cwd,startedAt:e.startedAt,lastActiveAt:e.lastActiveAt,cols:e.cols,rows:e.rows,state:e.state}}requireSession(e){const t=this.sessions.get(e);if(!t)throw new TerminalNotFoundError(e);return t}assertRunning(e){if("running"!==e.state)throw new TerminalClosedError(e.terminalId,e.state)}transitionState(e,t){const s=e.state;s!==t&&ALLOWED_STATE_TRANSITIONS[s].includes(t)&&(e.state=t,this.onStateChange?.({terminalId:e.terminalId,sessionId:e.sessionId,from:s,to:t}))}handleProcessData(e,t){const s=this.sessions.get(e);if(!s||"closed"===s.state)return;this.markActivity(s);const i=s.nextSeq++;s.output.push(t.toString("utf-8")),this.onOutput?.({terminalId:s.terminalId,sessionId:s.sessionId,data:t,seq:i})}handleProcessExit(e,t){const s=this.sessions.get(e);s&&(this.clearTimers(s),s.exitCode=t.code??void 0,"closing"!==s.state&&this.transitionState(s,"closing"),this.transitionState(s,"closed"),this.onExit?.({terminalId:s.terminalId,sessionId:s.sessionId,code:t.code,signal:t.signal,...s.timeoutReason?{reason:s.timeoutReason}:{}}))}markActivity(e){e.lastActiveAt=Date.now(),"running"===e.state&&this.refreshIdleTimer(e)}evictOldest(){let e=null;for(const t of this.sessions.values()){if("closed"===t.state){e=t;break}(!e||t.lastActiveAt<e.lastActiveAt||t.lastActiveAt===e.lastActiveAt&&t.startedAt<e.startedAt)&&(e=t)}e&&this.release(e.terminalId)}refreshIdleTimer(e){e.idleTimer&&clearTimeout(e.idleTimer),e.idleTimer=setTimeout(()=>{"running"===e.state&&this.closeSession(e.terminalId,TERMINAL_TIMEOUT_REASONS.idle)},this.idleTimeoutMs),"object"==typeof e.idleTimer&&"unref"in e.idleTimer&&e.idleTimer.unref()}scheduleIdleTimeout(e){this.refreshIdleTimer(e)}scheduleMaxDuration(e){e.maxDurationTimer=setTimeout(()=>{"running"===e.state&&this.closeSession(e.terminalId,TERMINAL_TIMEOUT_REASONS.maxDuration)},this.maxDurationMs),"object"==typeof e.maxDurationTimer&&"unref"in e.maxDurationTimer&&e.maxDurationTimer.unref()}clearTimers(e){e.idleTimer&&(clearTimeout(e.idleTimer),e.idleTimer=void 0),e.maxDurationTimer&&(clearTimeout(e.maxDurationTimer),e.maxDurationTimer=void 0)}}
|
|
1
|
+
import{spawn}from"node:child_process";import{createRequire}from"node:module";import{randomUUID}from"node:crypto";import{realpathSync}from"node:fs";import{dirname,resolve}from"node:path";export const TERMINAL_TIMEOUT_REASONS={idle:"idle_timeout",maxDuration:"max_duration_timeout"};const ALLOWED_STATE_TRANSITIONS={opening:["running","closing","closed"],running:["closing","closed"],closing:["closed"],closed:[]},require=createRequire(import.meta.url);let cachedNodePty;function resolveOpenclawNodeModulesDir(){const e=process.argv[1];if(e)try{const t=realpathSync(e);if(/openclaw/i.test(t))return dirname(t)}catch{}const t=["/opt/homebrew/lib/node_modules/openclaw","/usr/local/lib/node_modules/openclaw","/usr/lib/node_modules/openclaw"];for(const e of t)try{return realpathSync(resolve(e,"package.json")),e}catch{}return null}function getNodePty(){if(void 0!==cachedNodePty)return cachedNodePty;const e=["node-pty","@lydell/node-pty"];for(const t of e)try{return cachedNodePty=require(t),cachedNodePty}catch{}const t=resolveOpenclawNodeModulesDir();if(t){const s=createRequire(resolve(t,"package.json"));for(const t of e)try{return cachedNodePty=s(t),cachedNodePty}catch{}}return cachedNodePty=null,null}function createPtyProcess(e){const t=getNodePty();if(!t)throw new Error("node-pty not available");const s={...process.env,TERM:"xterm-256color",LANG:process.env.LANG||"C.UTF-8",LC_ALL:process.env.LC_ALL||"C.UTF-8"},i=t.spawn(e.shell,[],{name:"xterm-256color",cwd:e.cwd,env:s,cols:e.cols,rows:e.rows});let o=!1;return i.onData(t=>{e.onData(Buffer.from(t,"utf8"))}),i.onExit(t=>{var s;s={code:Number.isFinite(t.exitCode)?t.exitCode:null,signal:null!=t.signal?String(t.signal):null},o||(o=!0,e.onExit(s))}),{write(e){const t=e.toString("utf8");t&&i.write(t)},resize(e,t){i.resize(e,t)},close(){try{i.kill()}catch{}}}}function createPipeProcess(e){const t=spawn(e.shell,[],{shell:!0,cwd:e.cwd,stdio:["pipe","pipe","pipe"],env:{...process.env,TERM:"xterm-256color",LANG:process.env.LANG||"C.UTF-8",LC_ALL:process.env.LC_ALL||"C.UTF-8"}});let s=!1;const i=t=>{s||(s=!0,e.onExit(t))};return t.stdout?.on("data",t=>e.onData(Buffer.isBuffer(t)?t:Buffer.from(t))),t.stderr?.on("data",t=>e.onData(Buffer.isBuffer(t)?t:Buffer.from(t))),t.once("error",e=>i({code:null,signal:null})),t.once("exit",(e,t)=>i({code:e??null,signal:t??null})),{write(e){t.stdin?.destroyed||t.stdin?.write(e)},resize(e,t){},close(){t.killed||t.kill()}}}function createDefaultProcess(e){try{return createPtyProcess(e)}catch{return createPipeProcess(e)}}const DEFAULT_MAX_CONCURRENT=10,DEFAULT_IDLE_TIMEOUT_MS=432e5,DEFAULT_MAX_DURATION_MS=432e5;export class TerminalNotFoundError extends Error{terminalId;constructor(e){super(`terminal not found: ${e}`),this.name="TerminalNotFoundError",this.terminalId=e}}export class TerminalClosedError extends Error{terminalId;state;constructor(e,t){super(`terminal is not writable in state=${t}: ${e}`),this.name="TerminalClosedError",this.terminalId=e,this.state=t}}function chainHandler(e,t){return t?e?s=>{e(s),t(s)}:t:e}export class TerminalManager{sessions=new Map;maxConcurrent;idleTimeoutMs;maxDurationMs;onOutput;onExit;onStateChange;constructor(e){this.maxConcurrent=e?.maxConcurrent??10,this.idleTimeoutMs=e?.idleTimeoutMs??432e5,this.maxDurationMs=e?.maxDurationMs??432e5}addListeners(e){this.onOutput=chainHandler(this.onOutput,e.onOutput),this.onExit=chainHandler(this.onExit,e.onExit),this.onStateChange=chainHandler(this.onStateChange,e.onStateChange)}openSession(e){if(this.sessions.size>=this.maxConcurrent&&this.evictOldest(),this.sessions.size>=this.maxConcurrent)throw new Error("terminal quota exceeded");const t=e.terminalId&&!this.sessions.has(e.terminalId)?e.terminalId:`term_${randomUUID().slice(0,12)}`,s=e.sessionId??t,i=e.command||process.env.SHELL||"/bin/bash",o=e.cwd||process.cwd(),n=Date.now(),r={terminalId:t,sessionId:s,shell:i,cwd:o,startedAt:n,lastActiveAt:n,cols:e.cols,rows:e.rows,state:"opening",nextSeq:1,output:[],process:null};this.sessions.set(t,r);try{r.process=createDefaultProcess({shell:i,cwd:o,cols:e.cols,rows:e.rows,onData:e=>this.handleProcessData(t,e),onExit:e=>this.handleProcessExit(t,e)}),this.transitionState(r,"running"),this.scheduleIdleTimeout(r),this.scheduleMaxDuration(r)}catch(e){throw this.clearTimers(r),this.transitionState(r,"closed"),this.sessions.delete(t),e}return this.snapshot(r)}writeToSession(e,t){const s=this.requireSession(e);this.assertRunning(s),s.process?.write(t),this.markActivity(s)}resizeSession(e,t,s){const i=this.requireSession(e);this.assertRunning(i),i.cols=t,i.rows=s,i.process?.resize(t,s),this.markActivity(i)}closeSession(e,t){const s=this.sessions.get(e);s&&(t&&!s.timeoutReason&&(s.timeoutReason=t),"closed"!==s.state?(this.clearTimers(s),"closing"!==s.state&&(this.transitionState(s,"closing"),s.process?.close())):this.clearTimers(s))}touchSession(e){const t=this.requireSession(e);this.assertRunning(t),this.markActivity(t)}getSession(e){const t=this.sessions.get(e);return t?this.snapshot(t):void 0}create(e){const t=this.openSession({command:e,cols:80,rows:24}),s=this.sessions.get(t.terminalId);return{id:s.terminalId,command:s.shell,state:s.state,output:s.output,exitCode:s.exitCode,seq:s.nextSeq-1}}get(e){const t=this.sessions.get(e);if(t)return{id:t.terminalId,state:t.state,output:t.output,exitCode:t.exitCode,seq:t.nextSeq-1}}write(e,t){try{return this.writeToSession(e,Buffer.from(t,"utf8")),!0}catch{return!1}}resize(e,t,s){try{return this.resizeSession(e,t,s),!0}catch{return!1}}kill(e,t="SIGTERM"){const s=this.sessions.get(e);s&&s.process&&("closing"!==s.state&&this.transitionState(s,"closing"),s.process.close())}release(e){const t=this.sessions.get(e);t&&(!t.process||"running"!==t.state&&"opening"!==t.state||this.kill(e),this.clearTimers(t),this.sessions.delete(e))}listSessions(){return[...this.sessions.values()].map(e=>this.snapshot(e))}get sessionCount(){return this.sessions.size}get maxConcurrentLimit(){return this.maxConcurrent}destroy(){for(const e of[...this.sessions.keys()])this.release(e)}snapshot(e){return{terminalId:e.terminalId,sessionId:e.sessionId,shell:e.shell,cwd:e.cwd,startedAt:e.startedAt,lastActiveAt:e.lastActiveAt,cols:e.cols,rows:e.rows,state:e.state}}requireSession(e){const t=this.sessions.get(e);if(!t)throw new TerminalNotFoundError(e);return t}assertRunning(e){if("running"!==e.state)throw new TerminalClosedError(e.terminalId,e.state)}transitionState(e,t){const s=e.state;s!==t&&ALLOWED_STATE_TRANSITIONS[s].includes(t)&&(e.state=t,this.onStateChange?.({terminalId:e.terminalId,sessionId:e.sessionId,from:s,to:t}))}handleProcessData(e,t){const s=this.sessions.get(e);if(!s||"closed"===s.state)return;this.markActivity(s);const i=s.nextSeq++;s.output.push(t.toString("utf-8")),this.onOutput?.({terminalId:s.terminalId,sessionId:s.sessionId,data:t,seq:i})}handleProcessExit(e,t){const s=this.sessions.get(e);s&&(this.clearTimers(s),s.exitCode=t.code??void 0,"closing"!==s.state&&this.transitionState(s,"closing"),this.transitionState(s,"closed"),this.onExit?.({terminalId:s.terminalId,sessionId:s.sessionId,code:t.code,signal:t.signal,...s.timeoutReason?{reason:s.timeoutReason}:{}}))}markActivity(e){e.lastActiveAt=Date.now(),"running"===e.state&&this.refreshIdleTimer(e)}evictOldest(){let e=null;for(const t of this.sessions.values()){if("closed"===t.state){e=t;break}(!e||t.lastActiveAt<e.lastActiveAt||t.lastActiveAt===e.lastActiveAt&&t.startedAt<e.startedAt)&&(e=t)}e&&this.release(e.terminalId)}refreshIdleTimer(e){e.idleTimer&&clearTimeout(e.idleTimer),e.idleTimer=setTimeout(()=>{"running"===e.state&&this.closeSession(e.terminalId,TERMINAL_TIMEOUT_REASONS.idle)},this.idleTimeoutMs),"object"==typeof e.idleTimer&&"unref"in e.idleTimer&&e.idleTimer.unref()}scheduleIdleTimeout(e){this.refreshIdleTimer(e)}scheduleMaxDuration(e){e.maxDurationTimer=setTimeout(()=>{"running"===e.state&&this.closeSession(e.terminalId,TERMINAL_TIMEOUT_REASONS.maxDuration)},this.maxDurationMs),"object"==typeof e.maxDurationTimer&&"unref"in e.maxDurationTimer&&e.maxDurationTimer.unref()}clearTimers(e){e.idleTimer&&(clearTimeout(e.idleTimer),e.idleTimer=void 0),e.maxDurationTimer&&(clearTimeout(e.maxDurationTimer),e.maxDurationTimer=void 0)}}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "beeos-claw",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.7",
|
|
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>",
|