aws-runtime-bridge 1.9.100 → 1.9.102
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.
- package/dist/browser/browser-bootstrap.d.ts +11 -0
- package/dist/browser/browser-bootstrap.js +4 -0
- package/dist/browser/browser-connect-token.d.ts +10 -0
- package/dist/browser/browser-connect-token.js +2 -0
- package/dist/browser/browser-pool.d.ts +3 -1
- package/dist/browser/browser-pool.js +1 -1
- package/dist/browser/browser-ws-bridge.d.ts +12 -0
- package/dist/browser/browser-ws-bridge.js +6 -0
- package/dist/index.js +1 -1
- package/dist/remote-desktop/computer-control.js +1 -1
- package/dist/remote-desktop/frame-encode.d.ts +3 -0
- package/dist/remote-desktop/frame-encode.js +2 -0
- package/dist/remote-desktop/index.js +2 -2
- package/dist/remote-desktop/screen-capture.d.ts +80 -5
- package/dist/remote-desktop/screen-capture.js +1 -1
- package/dist/remote-desktop/session-manager.d.ts +3 -2
- package/dist/remote-desktop/session-manager.js +1 -1
- package/dist/remote-desktop/system-backend.js +1 -1
- package/dist/remote-desktop/types.d.ts +25 -0
- package/dist/routes/browser-sessions.js +1 -1
- package/dist/services/tunnel-client.js +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
export declare function isBrowserMissingError(error: unknown): boolean;
|
|
4
|
+
export interface BrowserInstallResult {
|
|
5
|
+
ok: boolean;
|
|
6
|
+
output: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export declare function installPlaywrightChromium(): Promise<BrowserInstallResult>;
|
|
10
|
+
|
|
11
|
+
export declare function formatBrowserLaunchError(error: Error, install: BrowserInstallResult): Error;
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import{execFile as n}from"node:child_process";import{createRequire as a}from"node:module";import{dirname as m,join as u}from"node:path";import{promisify as c}from"node:util";import{createLogger as l}from"../utils/logger.js";const s=l("browser-bootstrap"),h=c(n),p=a(import.meta.url),g=/executable doesn't exist/i,w=/missing dependencies|shared librar|libnss3|error while loading/i,d=10*60*1e3;function $(r){return r instanceof Error&&g.test(r.message)}function f(){const r=p.resolve("playwright/package.json");return u(m(r),"cli.js")}async function C(){const r=f();s.info(`\u6B63\u5728\u81EA\u52A8\u5B89\u88C5 Playwright Chromium\uFF08\u9996\u6B21\u4F7F\u7528\u9700\u4E0B\u8F7D\uFF0C\u53EF\u80FD\u8017\u65F6\u51E0\u5206\u949F\uFF09: node ${r} install chromium`);try{const{stdout:t,stderr:i}=await h(process.execPath,[r,"install","chromium"],{timeout:d,maxBuffer:16777216,windowsHide:!0});return s.info("Playwright Chromium \u81EA\u52A8\u5B89\u88C5\u5B8C\u6210"),{ok:!0,output:`${t??""}${i??""}`.trim()}}catch(t){const i=t,o=`${i.stdout??""}${i.stderr??""}`.trim()||i.message||String(t);return s.warn(`Playwright Chromium \u81EA\u52A8\u5B89\u88C5\u5931\u8D25: ${o.slice(0,500)}`),{ok:!1,output:o}}}function _(r,t){const i=`${t.output}
|
|
2
|
+
${r.message}`,o=w.test(i),e=[`\u6D4F\u89C8\u5668\u542F\u52A8\u5931\u8D25: ${r.message}`];return o?e.push("\u68C0\u6D4B\u5230\u7CFB\u7EDF\u7F3A\u5C11 Chromium \u8FD0\u884C\u6240\u9700\u4F9D\u8D56\uFF08\u5E38\u89C1\u4E8E\u7CBE\u7B80 Linux \u670D\u52A1\u5668\uFF09\u3002\u8BF7\u4EE5 root \u6267\u884C\u5B89\u88C5\u7CFB\u7EDF\u4F9D\u8D56\u5E76\u91CD\u8BD5\uFF1A"," sudo npx playwright install --with-deps chromium"):t.ok?e.push("\u6D4F\u89C8\u5668\u5DF2\u81EA\u52A8\u5B89\u88C5\u4F46\u542F\u52A8\u4ECD\u5931\u8D25\uFF0C\u8BF7\u68C0\u67E5\u78C1\u76D8\u7A7A\u95F4\u6216\u7CFB\u7EDF\u4F9D\u8D56\uFF0C\u53EF\u624B\u52A8\u6267\u884C\uFF1A"," sudo npx playwright install --with-deps chromium"):e.push("Playwright Chromium \u81EA\u52A8\u5B89\u88C5\u5931\u8D25\uFF0C\u8BF7\u624B\u52A8\u6267\u884C\uFF1A"," npx playwright install chromium","\uFF08\u82E5\u63D0\u793A\u7F3A\u5C11\u7CFB\u7EDF\u5E93\uFF0C\u5219\u6267\u884C\uFF1Asudo npx playwright install --with-deps chromium\uFF09"),new Error(e.join(`
|
|
3
|
+
`))}export{_ as formatBrowserLaunchError,C as installPlaywrightChromium,$ as isBrowserMissingError};
|
|
4
|
+
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
export declare function createBrowserConnectToken(agentId: string, browserId?: string): {
|
|
4
|
+
token: string;
|
|
5
|
+
expiresAt: string;
|
|
6
|
+
} | undefined;
|
|
7
|
+
|
|
8
|
+
export declare function consumeBrowserConnectToken(agentId: string, browserId: string, token: string): boolean;
|
|
9
|
+
|
|
10
|
+
export declare function deleteBrowserConnectTokensForAgent(agentId: string): void;
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import i from"node:crypto";import{DEFAULT_BROWSER_ID as c}from"./types.js";const f=60*1e3,n=new Map;function u(o,e){const t=e&&e.trim()?e.trim():c,r=`browserws_${i.randomBytes(32).toString("base64url")}`,s=Date.now()+f;return n.set(r,{agentId:o,browserId:t,expiresAt:s}),{token:r,expiresAt:new Date(s).toISOString()}}function w(o,e,t){const r=n.get(t);return r?r.expiresAt<Date.now()?(n.delete(t),!1):r.agentId!==o||r.browserId!==e?!1:(n.delete(t),!0):!1}function d(o){for(const[e,t]of n.entries())t.agentId===o&&n.delete(e)}export{w as consumeBrowserConnectToken,u as createBrowserConnectToken,d as deleteBrowserConnectTokensForAgent};
|
|
2
|
+
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
|
|
2
2
|
import type { Page } from "playwright";
|
|
3
|
-
import { type BrowserPoolConfig, type SessionBrowser } from "./types.js";
|
|
4
3
|
import type { ListenerTriggerEvent, PageListener } from "./page-listener-types.js";
|
|
4
|
+
import { type BrowserPoolConfig, type SessionBrowser } from "./types.js";
|
|
5
5
|
|
|
6
6
|
export interface SessionSummary {
|
|
7
7
|
agentId: string;
|
|
@@ -38,6 +38,8 @@ export declare class BrowserPool {
|
|
|
38
38
|
|
|
39
39
|
launch(agentId: string, browserId?: string, title?: string, scopeKey?: string): Promise<SessionBrowser>;
|
|
40
40
|
|
|
41
|
+
private launchChromium;
|
|
42
|
+
|
|
41
43
|
get(agentId: string, browserId?: string): SessionBrowser | undefined;
|
|
42
44
|
|
|
43
45
|
getOrLaunch(agentId: string, browserId?: string, title?: string, scopeKey?: string): Promise<SessionBrowser>;
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{chromium as M}from"playwright";import{DEFAULT_BROWSER_ID as h,IDLE_CHECK_INTERVAL_MS as _,IDLE_TIMEOUT_MS as C,MAX_PAGES_PER_SESSION as D,MAX_SESSIONS as x,MAX_SESSIONS_PER_AGENT as E,SESSION_TIMEOUT_MS as $,makeBrowserKey as k}from"./types.js";import{ScreencastManager as H}from"./screencast-manager.js";import{StepArchive as b}from"./step-archive.js";import{BrowserProfileStore as O}from"./browser-profile-store.js";import{ListenerStore as L}from"./listener-store.js";import{PageListenerManager as N}from"./page-listener-manager.js";import{buildStealthInitScript as B,STEALTH_LAUNCH_ARGS as R,STEALTH_IGNORE_ARGS as K}from"./stealth.js";const U=1280,z=800;class G{onSessionClose(e){this.sessionCloseHandlers.push(e)}setListenerTriggerHook(e){this.listenerTriggerHook=e}resolveKey(e,t){if(!e||typeof e!="string")throw new Error("scopeKey must be a non-empty string");const i=t&&t.trim()?t.trim():h;if(i.includes("::"))throw new Error(`browserId must not contain "::" (reserved separator): ${i}`);return k(e,i)}constructor(e){this.sessions=new Map,this.idleTimer=null,this.sessionCloseHandlers=[],this.listenerTriggerHook=null,this.config={headless:e?.headless??!0,executablePath:e?.executablePath,viewportWidth:e?.viewportWidth??U,viewportHeight:e?.viewportHeight??z,maxPagesPerSession:e?.maxPagesPerSession??D,maxSessionDurationMs:e?.maxSessionDurationMs??$,idleTimeoutMs:e?.idleTimeoutMs??C,launchArgs:e?.launchArgs,screencastConfig:e?.screencastConfig,archiveConfig:e?.archiveConfig,ssrfConfig:e?.ssrfConfig,userAgent:e?.userAgent,locale:e?.locale,timezone:e?.timezone,profileDir:e?.profileDir,stealth:e?.stealth??!0},this.profileStore=e?.profileDir?new O({profileDir:e.profileDir}):null,this.listenerStore=e?.profileDir?new L({listenerDir:e.profileDir}):null}async launch(e,t,i,s){const r=s??`agent:${e}`,n=this.resolveKey(r,t);if(this.sessions.has(n))throw new Error(`Browser session already exists for ${e}/${t??h}`);if(this.sessions.size>=x)throw new Error(`BrowserPool reached MAX_SESSIONS=${x}; close existing sessions before launching new ones`);let c=0;for(const o of this.sessions.values())o.agentId===e&&(c+=1);if(c>=E)throw new Error(`BrowserPool reached MAX_SESSIONS_PER_AGENT=${E} for agent ${e}; close some browser sessions first`);const l={headless:this.config.headless};this.config.executablePath&&(l.executablePath=this.config.executablePath),this.config.stealth?(l.ignoreDefaultArgs=[...K],l.args=[...R,...this.config.launchArgs??[]]):this.config.launchArgs&&this.config.launchArgs.length>0&&(l.args=this.config.launchArgs);const p=await M.launch(l);let m=this.config.userAgent,y;if(this.config.stealth)try{const o=await p.newBrowserCDPSession(),f=await o.send("Browser.getVersion"),g=f.product?.match(/Chrome\/(\d+)/);g&&(y=g[1]),!m&&f.userAgent&&(m=String(f.userAgent).replace(/HeadlessChrome\/([\d.]+)/g,"Chrome/$1")),await o.detach().catch(()=>{})}catch{}const w={viewport:{width:this.config.viewportWidth,height:this.config.viewportHeight}};if(m&&(w.userAgent=m),this.config.locale&&(w.locale=this.config.locale),this.config.timezone&&(w.timezoneId=this.config.timezone),this.profileStore)try{let o=await this.profileStore.load(r);!o&&r!==e&&(o=await this.profileStore.load(e),o&&await this.profileStore.save(r,o)),o&&(w.storageState=o)}catch{}const v=await p.newContext(w).catch(async o=>{throw await p.close().catch(()=>{}),o});this.config.stealth&&await v.addInitScript(B(y??"126")).catch(()=>{});const S=await v.newPage().catch(async o=>{throw await p.close().catch(()=>{}),o}),P=new H(this.config.screencastConfig),I=new b(this.config.archiveConfig),A=new N(e,t?.trim()||h,{onTrigger:(o,f)=>{const g=this.listenerTriggerHook;if(g)try{g(e,t?.trim()||h,o,f)}catch{}},store:this.listenerStore,scopeKey:r});try{await P.start(S)}catch{}try{const o=S;o.screencast?.showActions&&await o.screencast.showActions({cursor:"pointer"})}catch{}const T=Date.now(),a={agentId:e,scopeKey:r,browserId:t?.trim()||h,title:i?.trim()||"\u6D4F\u89C8\u5668\u4F1A\u8BDD",browser:p,context:v,pages:[S],activePageIndex:0,screencast:P,archive:I,listenerManager:A,launchedAt:T,lastActivityAt:T,humanTakeover:!1,qualityPreset:"original"};v.on("page",o=>{if(a.pages.length>=this.config.maxPagesPerSession){o.close().catch(()=>{});return}a.pages.push(o);const f=a.pages.length-1;this.setActivePage(e,f,a.browserId).catch(()=>{}),o.on("close",()=>{const g=a.pages.indexOf(o);if(g!==-1){if(a.pages.splice(g,1),a.pages.length===0){this.close(e,a.browserId).catch(()=>{});return}a.activePageIndex>=a.pages.length?a.activePageIndex=a.pages.length-1:g<a.activePageIndex&&(a.activePageIndex-=1)}})});try{await A.attach(S)}catch{}return this.sessions.set(n,a),this.ensureIdleTimer(),this.profileStore&&(a.autoSaveTimer=setInterval(async()=>{try{const o=await a.context.storageState();await this.profileStore.save(a.scopeKey,o)}catch{}},3e4),a.autoSaveTimer.unref&&a.autoSaveTimer.unref()),a}get(e,t){const i=t&&t.trim()?t.trim():h;for(const s of this.sessions.values())if(s.agentId===e&&s.browserId===i)return s}async getOrLaunch(e,t,i,s){const r=s??`agent:${e}`,n=this.resolveKey(r,t),c=this.sessions.get(n);return c||this.launch(e,t,i,s)}async close(e,t){const i=t&&t.trim()?t.trim():h;let s,r;for(const[c,l]of this.sessions)if(l.agentId===e&&l.browserId===i){s=c,r=l;break}if(!s||!r)return;const n=r;n.autoSaveTimer&&(clearInterval(n.autoSaveTimer),n.autoSaveTimer=void 0);try{await n.screencast.dispose()}catch{}try{await n.listenerManager.dispose()}catch{}if(n.archive.clear(),this.profileStore)try{const c=await n.context.storageState();await this.profileStore.save(n.scopeKey,c)}catch{}try{await n.browser.close()}catch{}this.sessions.delete(s);for(const c of this.sessionCloseHandlers)try{c(e,i)}catch{}this.sessions.size===0&&this.stopIdleTimer()}async closeByAgent(e){if(!e)return;const t=[];for(const[i,s]of this.sessions)s.agentId===e&&t.push(i);await Promise.all(t.map(i=>{const r=this.sessions.get(i)?.browserId;return this.close(e,r)}))}async collectListenerEvents(e){if(!e)return[];const t=[];for(const i of this.sessions.values()){if(i.agentId!==e)continue;await i.listenerManager.scanExistsAtPoll();const s=i.listenerManager.collectEvents();s.length>0&&t.push(...s)}return t.sort((i,s)=>i.triggeredAt-s.triggeredAt),t}getActivePage(e,t){const i=this.requireSession(e,t),s=i.pages[i.activePageIndex];if(!s||s.isClosed())throw new Error(`No active page for agent ${e}/${i.browserId} (index ${i.activePageIndex})`);return s}async setActivePage(e,t,i){const s=this.requireSession(e,i);if(!Number.isInteger(t)||t<0||t>=s.pages.length)throw new Error(`Page index ${t} out of range (0..${s.pages.length-1}) for agent ${e}/${s.browserId}`);if(s.activePageIndex===t)return s.pages[t];const r=s.pages[s.activePageIndex];if(r&&!r.isClosed())try{await s.screencast.stop(r)}catch{}s.activePageIndex=t;const n=s.pages[t];try{await s.screencast.start(n)}catch{}try{await s.listenerManager.reattach(n)}catch{}return this.touchActivity(e,i),n}async newPage(e,t){const i=this.requireSession(e,t);if(i.pages.length>=this.config.maxPagesPerSession)throw new Error(`Agent ${e}/${i.browserId} reached maxPagesPerSession=${this.config.maxPagesPerSession}; close a tab first`);const s=await i.context.newPage();i.pages.push(s);const r=i.pages.length-1;return this.touchActivity(e,t),this.setActivePage(e,r,t)}async closePage(e,t,i){const s=this.requireSession(e,i);if(!Number.isInteger(t)||t<0||t>=s.pages.length)throw new Error(`Page index ${t} out of range (0..${s.pages.length-1}) for agent ${e}/${s.browserId}`);const r=s.pages[t];if(t===s.activePageIndex)try{await s.screencast.stop(r)}catch{}try{await r.close()}catch{}if(s.pages.splice(t,1),s.pages.length===0){await this.close(e,i);return}s.activePageIndex>=s.pages.length?s.activePageIndex=s.pages.length-1:t<s.activePageIndex&&(s.activePageIndex-=1);const n=s.pages[s.activePageIndex];if(!n.isClosed()){try{await s.screencast.start(n)}catch{}try{await s.listenerManager.reattach(n)}catch{}}this.touchActivity(e,i)}listSessions(e){const t=[];for(const i of this.sessions.values()){if(e&&i.agentId!==e)continue;const s=i.pages[i.activePageIndex];let r="",n="";if(s&&!s.isClosed())try{r=s.url()}catch{}t.push({agentId:i.agentId,browserId:i.browserId,title:i.title,pageCount:i.pages.length,activePageIndex:i.activePageIndex,launchedAt:i.launchedAt,lastActivityAt:i.lastActivityAt,currentUrl:r,currentTitle:n})}return t}touchActivity(e,t){const i=t&&t.trim()?t.trim():h;for(const s of this.sessions.values())if(s.agentId===e&&s.browserId===i){s.lastActivityAt=Date.now();return}}async dispose(){this.stopIdleTimer();const e=Array.from(this.sessions.keys());await Promise.all(e.map(t=>{const i=this.sessions.get(t);return i?this.close(i.agentId,i.browserId):Promise.resolve()}))}requireSession(e,t){const i=t&&t.trim()?t.trim():h;for(const s of this.sessions.values())if(s.agentId===e&&s.browserId===i)return s;throw new Error(`Browser session not launched for ${e}/${t??h}`)}ensureIdleTimer(){this.idleTimer||this.config.idleTimeoutMs<=0&&this.config.maxSessionDurationMs<=0||(this.idleTimer=setInterval(()=>{this.checkIdleSessions()},_),this.idleTimer.unref&&this.idleTimer.unref())}stopIdleTimer(){this.idleTimer&&(clearInterval(this.idleTimer),this.idleTimer=null)}async checkIdleSessions(){const e=Date.now(),t=[];for(const i of this.sessions.values()){if(i.humanTakeover)continue;const s=e-i.lastActivityAt,r=e-i.launchedAt;if(this.config.idleTimeoutMs>0&&s>this.config.idleTimeoutMs){t.push(i);continue}this.config.maxSessionDurationMs>0&&r>this.config.maxSessionDurationMs&&t.push(i)}await Promise.all(t.map(i=>this.close(i.agentId,i.browserId)))}}let u=null;function Y(d){return u||(u=new G(d)),u}async function Z(){u&&(await u.dispose(),u=null)}export{G as BrowserPool,Z as disposeBrowserPool,Y as getBrowserPool};
|
|
1
|
+
import{chromium as E}from"playwright";import{formatBrowserLaunchError as C,installPlaywrightChromium as _,isBrowserMissingError as D}from"./browser-bootstrap.js";import{BrowserProfileStore as $}from"./browser-profile-store.js";import{ListenerStore as b}from"./listener-store.js";import{PageListenerManager as k}from"./page-listener-manager.js";import{ScreencastManager as H}from"./screencast-manager.js";import{buildStealthInitScript as B,STEALTH_LAUNCH_ARGS as L,STEALTH_IGNORE_ARGS as O}from"./stealth.js";import{StepArchive as N}from"./step-archive.js";import{DEFAULT_BROWSER_ID as h,IDLE_CHECK_INTERVAL_MS as R,IDLE_TIMEOUT_MS as K,MAX_PAGES_PER_SESSION as U,MAX_SESSIONS as x,MAX_SESSIONS_PER_AGENT as I,SESSION_TIMEOUT_MS as z,makeBrowserKey as G}from"./types.js";const W=1280,q=800;class V{onSessionClose(e){this.sessionCloseHandlers.push(e)}setListenerTriggerHook(e){this.listenerTriggerHook=e}resolveKey(e,s){if(!e||typeof e!="string")throw new Error("scopeKey must be a non-empty string");const i=s&&s.trim()?s.trim():h;if(i.includes("::"))throw new Error(`browserId must not contain "::" (reserved separator): ${i}`);return G(e,i)}constructor(e){this.sessions=new Map,this.idleTimer=null,this.sessionCloseHandlers=[],this.listenerTriggerHook=null,this.config={headless:e?.headless??!0,executablePath:e?.executablePath,viewportWidth:e?.viewportWidth??W,viewportHeight:e?.viewportHeight??q,maxPagesPerSession:e?.maxPagesPerSession??U,maxSessionDurationMs:e?.maxSessionDurationMs??z,idleTimeoutMs:e?.idleTimeoutMs??K,launchArgs:e?.launchArgs,screencastConfig:e?.screencastConfig,archiveConfig:e?.archiveConfig,ssrfConfig:e?.ssrfConfig,userAgent:e?.userAgent,locale:e?.locale,timezone:e?.timezone,profileDir:e?.profileDir,stealth:e?.stealth??!0},this.profileStore=e?.profileDir?new $({profileDir:e.profileDir}):null,this.listenerStore=e?.profileDir?new b({listenerDir:e.profileDir}):null}async launch(e,s,i,t){const r=t??`agent:${e}`,n=this.resolveKey(r,s);if(this.sessions.has(n))throw new Error(`Browser session already exists for ${e}/${s??h}`);if(this.sessions.size>=x)throw new Error(`BrowserPool reached MAX_SESSIONS=${x}; close existing sessions before launching new ones`);let c=0;for(const o of this.sessions.values())o.agentId===e&&(c+=1);if(c>=I)throw new Error(`BrowserPool reached MAX_SESSIONS_PER_AGENT=${I} for agent ${e}; close some browser sessions first`);const l={headless:this.config.headless};this.config.executablePath&&(l.executablePath=this.config.executablePath),this.config.stealth?(l.ignoreDefaultArgs=[...O],l.args=[...L,...this.config.launchArgs??[]]):this.config.launchArgs&&this.config.launchArgs.length>0&&(l.args=this.config.launchArgs);const p=await this.launchChromium(l);let m=this.config.userAgent,y;if(this.config.stealth)try{const o=await p.newBrowserCDPSession(),f=await o.send("Browser.getVersion"),g=f.product?.match(/Chrome\/(\d+)/);g&&(y=g[1]),!m&&f.userAgent&&(m=String(f.userAgent).replace(/HeadlessChrome\/([\d.]+)/g,"Chrome/$1")),await o.detach().catch(()=>{})}catch{}const w={viewport:{width:this.config.viewportWidth,height:this.config.viewportHeight}};if(m&&(w.userAgent=m),this.config.locale&&(w.locale=this.config.locale),this.config.timezone&&(w.timezoneId=this.config.timezone),this.profileStore)try{let o=await this.profileStore.load(r);!o&&r!==e&&(o=await this.profileStore.load(e),o&&await this.profileStore.save(r,o)),o&&(w.storageState=o)}catch{}const S=await p.newContext(w).catch(async o=>{throw await p.close().catch(()=>{}),o});this.config.stealth&&await S.addInitScript(B(y??"126")).catch(()=>{});const v=await S.newPage().catch(async o=>{throw await p.close().catch(()=>{}),o}),P=new H(this.config.screencastConfig),M=new N(this.config.archiveConfig),A=new k(e,s?.trim()||h,{onTrigger:(o,f)=>{const g=this.listenerTriggerHook;if(g)try{g(e,s?.trim()||h,o,f)}catch{}},store:this.listenerStore,scopeKey:r});try{await P.start(v)}catch{}try{const o=v;o.screencast?.showActions&&await o.screencast.showActions({cursor:"pointer"})}catch{}const T=Date.now(),a={agentId:e,scopeKey:r,browserId:s?.trim()||h,title:i?.trim()||"\u6D4F\u89C8\u5668\u4F1A\u8BDD",browser:p,context:S,pages:[v],activePageIndex:0,screencast:P,archive:M,listenerManager:A,launchedAt:T,lastActivityAt:T,humanTakeover:!1,qualityPreset:"original"};S.on("page",o=>{if(a.pages.length>=this.config.maxPagesPerSession){o.close().catch(()=>{});return}a.pages.push(o);const f=a.pages.length-1;this.setActivePage(e,f,a.browserId).catch(()=>{}),o.on("close",()=>{const g=a.pages.indexOf(o);if(g!==-1){if(a.pages.splice(g,1),a.pages.length===0){this.close(e,a.browserId).catch(()=>{});return}a.activePageIndex>=a.pages.length?a.activePageIndex=a.pages.length-1:g<a.activePageIndex&&(a.activePageIndex-=1)}})});try{await A.attach(v)}catch{}return this.sessions.set(n,a),this.ensureIdleTimer(),this.profileStore&&(a.autoSaveTimer=setInterval(async()=>{try{const o=await a.context.storageState();await this.profileStore.save(a.scopeKey,o)}catch{}},3e4),a.autoSaveTimer.unref&&a.autoSaveTimer.unref()),a}async launchChromium(e){try{return await E.launch(e)}catch(s){if(this.config.executablePath||!D(s))throw s;const i=await _();try{return await E.launch(e)}catch(t){throw C(t instanceof Error?t:new Error(String(t)),i)}}}get(e,s){const i=s&&s.trim()?s.trim():h;for(const t of this.sessions.values())if(t.agentId===e&&t.browserId===i)return t}async getOrLaunch(e,s,i,t){const r=t??`agent:${e}`,n=this.resolveKey(r,s),c=this.sessions.get(n);return c||this.launch(e,s,i,t)}async close(e,s){const i=s&&s.trim()?s.trim():h;let t,r;for(const[c,l]of this.sessions)if(l.agentId===e&&l.browserId===i){t=c,r=l;break}if(!t||!r)return;const n=r;n.autoSaveTimer&&(clearInterval(n.autoSaveTimer),n.autoSaveTimer=void 0);try{await n.screencast.dispose()}catch{}try{await n.listenerManager.dispose()}catch{}if(n.archive.clear(),this.profileStore)try{const c=await n.context.storageState();await this.profileStore.save(n.scopeKey,c)}catch{}try{await n.browser.close()}catch{}this.sessions.delete(t);for(const c of this.sessionCloseHandlers)try{c(e,i)}catch{}this.sessions.size===0&&this.stopIdleTimer()}async closeByAgent(e){if(!e)return;const s=[];for(const[i,t]of this.sessions)t.agentId===e&&s.push(i);await Promise.all(s.map(i=>{const r=this.sessions.get(i)?.browserId;return this.close(e,r)}))}async collectListenerEvents(e){if(!e)return[];const s=[];for(const i of this.sessions.values()){if(i.agentId!==e)continue;await i.listenerManager.scanExistsAtPoll();const t=i.listenerManager.collectEvents();t.length>0&&s.push(...t)}return s.sort((i,t)=>i.triggeredAt-t.triggeredAt),s}getActivePage(e,s){const i=this.requireSession(e,s),t=i.pages[i.activePageIndex];if(!t||t.isClosed())throw new Error(`No active page for agent ${e}/${i.browserId} (index ${i.activePageIndex})`);return t}async setActivePage(e,s,i){const t=this.requireSession(e,i);if(!Number.isInteger(s)||s<0||s>=t.pages.length)throw new Error(`Page index ${s} out of range (0..${t.pages.length-1}) for agent ${e}/${t.browserId}`);if(t.activePageIndex===s)return t.pages[s];const r=t.pages[t.activePageIndex];if(r&&!r.isClosed())try{await t.screencast.stop(r)}catch{}t.activePageIndex=s;const n=t.pages[s];try{await t.screencast.start(n)}catch{}try{await t.listenerManager.reattach(n)}catch{}return this.touchActivity(e,i),n}async newPage(e,s){const i=this.requireSession(e,s);if(i.pages.length>=this.config.maxPagesPerSession)throw new Error(`Agent ${e}/${i.browserId} reached maxPagesPerSession=${this.config.maxPagesPerSession}; close a tab first`);const t=await i.context.newPage();i.pages.push(t);const r=i.pages.length-1;return this.touchActivity(e,s),this.setActivePage(e,r,s)}async closePage(e,s,i){const t=this.requireSession(e,i);if(!Number.isInteger(s)||s<0||s>=t.pages.length)throw new Error(`Page index ${s} out of range (0..${t.pages.length-1}) for agent ${e}/${t.browserId}`);const r=t.pages[s];if(s===t.activePageIndex)try{await t.screencast.stop(r)}catch{}try{await r.close()}catch{}if(t.pages.splice(s,1),t.pages.length===0){await this.close(e,i);return}t.activePageIndex>=t.pages.length?t.activePageIndex=t.pages.length-1:s<t.activePageIndex&&(t.activePageIndex-=1);const n=t.pages[t.activePageIndex];if(!n.isClosed()){try{await t.screencast.start(n)}catch{}try{await t.listenerManager.reattach(n)}catch{}}this.touchActivity(e,i)}listSessions(e){const s=[];for(const i of this.sessions.values()){if(e&&i.agentId!==e)continue;const t=i.pages[i.activePageIndex];let r="",n="";if(t&&!t.isClosed())try{r=t.url()}catch{}s.push({agentId:i.agentId,browserId:i.browserId,title:i.title,pageCount:i.pages.length,activePageIndex:i.activePageIndex,launchedAt:i.launchedAt,lastActivityAt:i.lastActivityAt,currentUrl:r,currentTitle:n})}return s}touchActivity(e,s){const i=s&&s.trim()?s.trim():h;for(const t of this.sessions.values())if(t.agentId===e&&t.browserId===i){t.lastActivityAt=Date.now();return}}async dispose(){this.stopIdleTimer();const e=Array.from(this.sessions.keys());await Promise.all(e.map(s=>{const i=this.sessions.get(s);return i?this.close(i.agentId,i.browserId):Promise.resolve()}))}requireSession(e,s){const i=s&&s.trim()?s.trim():h;for(const t of this.sessions.values())if(t.agentId===e&&t.browserId===i)return t;throw new Error(`Browser session not launched for ${e}/${s??h}`)}ensureIdleTimer(){this.idleTimer||this.config.idleTimeoutMs<=0&&this.config.maxSessionDurationMs<=0||(this.idleTimer=setInterval(()=>{this.checkIdleSessions()},R),this.idleTimer.unref&&this.idleTimer.unref())}stopIdleTimer(){this.idleTimer&&(clearInterval(this.idleTimer),this.idleTimer=null)}async checkIdleSessions(){const e=Date.now(),s=[];for(const i of this.sessions.values()){if(i.humanTakeover)continue;const t=e-i.lastActivityAt,r=e-i.launchedAt;if(this.config.idleTimeoutMs>0&&t>this.config.idleTimeoutMs){s.push(i);continue}this.config.maxSessionDurationMs>0&&r>this.config.maxSessionDurationMs&&s.push(i)}await Promise.all(s.map(i=>this.close(i.agentId,i.browserId)))}}let u=null;function se(d){return u||(u=new V(d)),u}async function ie(){u&&(await u.dispose(),u=null)}export{V as BrowserPool,ie as disposeBrowserPool,se as getBrowserPool};
|
|
2
2
|
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
|
|
2
|
+
import type { IncomingMessage } from "node:http";
|
|
3
|
+
import type { Socket } from "node:net";
|
|
4
|
+
|
|
5
|
+
export declare function parseBrowserBridgePath(pathname: string): {
|
|
6
|
+
agentId: string;
|
|
7
|
+
browserId: string;
|
|
8
|
+
} | null;
|
|
9
|
+
|
|
10
|
+
export declare function attachBrowserWebSocketServer(server: {
|
|
11
|
+
on: (event: "upgrade", listener: (request: IncomingMessage, socket: Socket, head: Buffer) => void) => unknown;
|
|
12
|
+
}): void;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import i,{WebSocketServer as R}from"ws";import{isRuntimeTokenValid as U}from"../middleware/auth.js";import{getBrowserWsPort as T}from"../services/browser-session-manager.js";import{createLogger as k}from"../utils/logger.js";import{consumeBrowserConnectToken as E}from"./browser-connect-token.js";import{DEFAULT_BROWSER_ID as S}from"./types.js";const u=k("browser-ws-bridge"),O=/^\/browser\/agent\/([^/]+)(?:\/([^/]+))?$/;function W(c){const o=O.exec(c);return o?{agentId:decodeURIComponent(o[1]||""),browserId:o[2]?decodeURIComponent(o[2]):S}:null}function B(c,o){const a=o.searchParams.get("token")||"";if(a.trim())return a.trim();const t=c.headers["x-runtime-token"];return Array.isArray(t)?t[0]?.trim()||"":typeof t=="string"?t.trim():String(c.headers.authorization||"").match(/^Bearer\s+(.+)$/i)?.[1]?.trim()||""}function L(c){const o=new R({noServer:!0});c.on("upgrade",(a,t,b)=>{const h=a.headers.host||"localhost",$=new URL(a.url||"/",`http://${h}`),p=W($.pathname);if(!p)return;const d=B(a,$),s=p.agentId,g=p.browserId,y=d?d.length>12?`${d.substring(0,12)}...`:d:"empty";if(u.info(`[browser-ws] upgrade \u8BF7\u6C42: agentId=${s}, browserId=${g}, token=${y}`),!U(d)&&!E(s,g,d)){u.warn(`[browser-ws] token \u6821\u9A8C\u5931\u8D25: agentId=${s}, browserId=${g}`),t.write(`HTTP/1.1 401 Unauthorized\r
|
|
2
|
+
\r
|
|
3
|
+
`),t.destroy();return}const I=T();if(!I){u.warn(`[browser-ws] browser \u5DE5\u5177\u6808\u672A\u542F\u52A8: agentId=${s}`),t.write(`HTTP/1.1 503 Service Unavailable\r
|
|
4
|
+
\r
|
|
5
|
+
`),t.destroy();return}const P=g===S?`/agent/${encodeURIComponent(s)}`:`/agent/${encodeURIComponent(s)}/${encodeURIComponent(g)}`,f=`ws://127.0.0.1:${I}${P}`;let e=null;const w=[];o.handleUpgrade(a,t,b,n=>{try{e=new i(f)}catch(r){u.warn(`[browser-ws] \u672C\u5730 WS \u8FDE\u63A5\u5931\u8D25: ${f}, err=${r.message}`),n.close(1011,"local ws connect failed");return}const l=()=>{e&&e.readyState!==i.CLOSED&&e.close()};e.on("open",()=>{u.info(`[browser-ws] \u5DF2\u8F6C\u53D1: agentId=${s}, browserId=${g}, upstream=${f}`);for(const r of w)e&&e.readyState===i.OPEN&&e.send(r.data,{binary:r.binary});w.length=0}),e.on("message",(r,m)=>{n.readyState===i.OPEN&&n.send(r,{binary:m})}),e.on("close",(r,m)=>{n.readyState===i.OPEN&&n.close(r,m.toString())}),e.on("error",r=>{u.warn(`[browser-ws] \u672C\u5730 WS \u9519\u8BEF: agentId=${s}, err=${r.message}`),l(),n.readyState===i.OPEN&&n.close(1011,r.message)}),n.on("message",(r,m)=>{e&&e.readyState===i.OPEN?e.send(r,{binary:m}):w.length<200&&w.push({data:r,binary:m})}),n.on("close",()=>{l()}),n.on("error",()=>{l()})})})}export{L as attachBrowserWebSocketServer,W as parseBrowserBridgePath};
|
|
6
|
+
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import I from"express";import D from"cors";import{existsSync as E,readFileSync as B}from"node:fs";import x from"node:path";import M from"node:os";import{allowedCorsOrigins as y,port as h,validateProductionToken as O}from"./config.js";import{propertiesRouter as G}from"./routes/properties.js";import{ymlRouter as _}from"./routes/yml.js";import{instanceRouter as F,setGracefulShutdownFn as K}from"./routes/instance.js";import{skillsRouter as L}from"./routes/skills.js";import{mcpRouter as N}from"./routes/mcp.js";import{terminalRouter as j,sdkSessions as S}from"./routes/terminal.js";import{sessionsRouter as U}from"./routes/sessions.js";import{gitRouter as W}from"./routes/git.js";import{fileSnapshotRouter as z}from"./routes/file-snapshot.js";import{acodeTodosRouter as J}from"./routes/acode-todos.js";import{backgroundTasksRouter as Y}from"./routes/background-tasks.js";import{subAgentsRouter as q}from"./routes/sub-agents.js";import{browserSessionsRouter as H}from"./routes/browser-sessions.js";import{browserListenersRouter as Z}from"./routes/browser-listeners.js";import{fileBrowserRouter as Q}from"./routes/file-browser.js";import{aiSourcesRouter as V}from"./routes/ai-sources.js";import{processesRouter as X}from"./routes/processes.js";import{systemMetricsRouter as ee}from"./routes/system-metrics.js";import{taskContextRouter as re}from"./routes/task-context.js";import{fileDiffRollbackRouter as te}from"./routes/file-diff-rollback.js";import{attachPtyWebSocketServer as ie,closeAllPtySessions as ne,ptyRouter as oe,ptySessions as se}from"./routes/pty.js";import{runtimeBindingRouter as ae,logRuntimeBindingStartupState as me}from"./routes/runtime-binding.js";import{dashboardRouter as ue}from"./routes/dashboard.js";import{eventsRouter as ce}from"./routes/events.js";import{attachRemoteDesktopWebSocketServer as de}from"./remote-desktop/index.js";import{loadPersistedSessions as P,savePersistedSessions as $}from"./services/terminal-persistence.js";import{detectOrphanProcesses as ge}from"./services/process-detector.js";import{startOrphanMonitor as pe,stopOrphanMonitor as fe}from"./services/orphan-monitor.js";import{getAgentProcessManager as k}from"./services/agent-process-manager.js";import{adapterRegistry as le}from"./adapter/index.js";import{logger as e}from"./utils/logger.js";import{autoRegister as be,unregister as we,bridgeRestartCleanup as he,cancelBackgroundRegisterRetries as Se}from"./services/auto-register.js";import{stopTunnelClient as Re}from"./services/tunnel-client.js";import{ensureAwsClientAgentMcpReleased as Ie}from"./services/aws-client-agent-mcp.js";import{ensureAcodePackageReleased as ye}from"./services/acode-package.js";import{ensureStartupConfig as Pe}from"./services/startup-config-wizard.js";import{handleCliCommand as $e}from"./services/cli-commands.js";import{migrateOldPathsOnStartup as ke}from"./services/legacy-path-migration.js";import{subAgentStore as Ce}from"./services/sub-agent-manager-instance.js";import{disposeBrowserSessionManager as Te}from"./services/browser-session-manager.js";import{buildGracefulShutdownPlan as ve,shouldCleanupPersistedSessionsOnStartup as Ae}from"./services/runtime-lifecycle-policy.js";import{deliverPendingBridgeLifecycleNotifications as De}from"./services/lifecycle-state.js";const C=await $e();C.handled&&process.exit(C.exitCode),await Pe(),O(),Ie(),ye(),ke(),me();async function Ee(){try{if(await be())e.info("[runtime-bridge] \u81EA\u52A8\u6CE8\u518C\u6210\u529F");else{const{loadConfig:i}=await import("./services/auto-register.js"),s=i();!!s.userKey&&s.enabled?e.warn("[runtime-bridge] \u81EA\u52A8\u6CE8\u518C\u6682\u65F6\u5931\u8D25\uFF0C\u5C06\u5728\u540E\u53F0\u6301\u7EED\u91CD\u8BD5\u76F4\u5230\u4E0E\u8C03\u5EA6\u4E2D\u5FC3\u5EFA\u7ACB\u8FDE\u63A5"):(e.info("[runtime-bridge] \u81EA\u52A8\u6CE8\u518C\u672A\u914D\u7F6E\uFF0C\u7EE7\u7EED\u542F\u52A8\u670D\u52A1"),e.info("[runtime-bridge] \u8981\u542F\u7528\u81EA\u52A8\u6CE8\u518C\uFF0C\u8BF7\u521B\u5EFA\u914D\u7F6E\u6587\u4EF6 ~/.aws-bridge/config.json"))}}catch(r){const i=r;e.warn("[runtime-bridge] \u81EA\u52A8\u6CE8\u518C\u51FA\u9519:",i.message)}}Ee().then(()=>Be()).then(()=>De()).then(()=>{e.info("[runtime-bridge] Bridge \u751F\u547D\u5468\u671F\u901A\u77E5\u540C\u6B65\u5B8C\u6210")}).catch(r=>{e.warn("[runtime-bridge] \u81EA\u52A8\u6CE8\u518C\u6216\u6E05\u7406\u51FA\u9519:",r)});async function Be(){try{if(process.env.AWS_BRIDGE_RESTART_CLEANUP!=="true"){e.info("[runtime-bridge] \u8DF3\u8FC7 Bridge \u91CD\u542F\u6E05\u7406\uFF0C\u4FDD\u7559 Agent \u72B6\u6001\u4F9B\u8C03\u5EA6\u4E2D\u5FC3\u6062\u590D");return}const{getRegistrationState:r}=await import("./services/auto-register.js"),i=r();if(!i.registered||!i.instanceId){e.info("[runtime-bridge] \u5B9E\u4F8B\u672A\u6CE8\u518C\uFF0C\u8DF3\u8FC7 Bridge \u91CD\u542F\u6E05\u7406");return}e.info("[runtime-bridge] \u2605 Bridge \u91CD\u542F\u6E05\u7406\u5DF2\u663E\u5F0F\u542F\u7528\uFF0C\u6B63\u5728\u901A\u77E5\u8C03\u5EA6\u4E2D\u5FC3\u6E05\u7406 Agent \u72B6\u6001...");const s=await he();s.success?e.info(`[runtime-bridge] \u2605 Bridge \u91CD\u542F\u6E05\u7406\u6210\u529F\uFF0C\u6E05\u7406\u4E86 ${s.agentCount||0} \u4E2A Agent`):e.warn(`[runtime-bridge] \u2605 Bridge \u91CD\u542F\u6E05\u7406\u5931\u8D25: ${s.error}`)}catch(r){const i=r;e.warn("[runtime-bridge] Bridge \u91CD\u542F\u6E05\u7406\u51FA\u9519:",i.message)}}async function xe(){try{const r=await P();if(r.length===0){e.info("[runtime-bridge] \u65E0\u6301\u4E45\u5316\u4F1A\u8BDD\uFF0C\u8DF3\u8FC7\u8FDB\u7A0B\u6CE8\u518C\u8868\u91CD\u5EFA");return}await k().rebuildRegistry(r.map(s=>({agentId:s.agentId,sessionId:s.sessionId,pid:s.pid,workspacePath:s.workspacePath,command:s.command,mode:"sdk"}))),e.info(`[runtime-bridge] \u8FDB\u7A0B\u6CE8\u518C\u8868\u91CD\u5EFA\u5B8C\u6210: ${r.length} \u6761\u8BB0\u5F55`)}catch(r){const i=r;e.warn("[runtime-bridge] \u8FDB\u7A0B\u6CE8\u518C\u8868\u91CD\u5EFA\u5931\u8D25:",i.message)}}xe().catch(r=>{e.warn("[runtime-bridge] \u8FDB\u7A0B\u6CE8\u518C\u8868\u91CD\u5EFA\u51FA\u9519:",r)});async function Me(){try{const r=await import("node:fs/promises"),i=await import("node:path"),s=await import("node:os"),f=i.join(s.tmpdir(),"agentswork-runtime-bridge",".preserve-sessions");let u=!1;try{const t=await r.readFile(f,"utf-8");u=JSON.parse(t).preserveSessions===!0,await r.unlink(f),e.info(`[runtime-bridge] \u2605 \u68C0\u6D4B\u5230\u4FDD\u7559\u4F1A\u8BDD\u6807\u8BB0\uFF0CpreserveSessions=${u}`)}catch{}const a=await P();if(a.length===0){e.info("[runtime-bridge] \u542F\u52A8\u65F6\u65E0\u6301\u4E45\u5316\u4F1A\u8BDD\uFF0C\u65E0\u9700\u68C0\u6D4B\u5B64\u513F\u8FDB\u7A0B");return}if(!Ae(u)){e.info(`[runtime-bridge] \u2605 \u4FDD\u7559 ${a.length} \u4E2A\u6301\u4E45\u5316\u4F1A\u8BDD\u5E76\u91CD\u5EFA\u8FDB\u7A0B\u6CE8\u518C\u8868`),await k().rebuildRegistry(a.map(o=>({agentId:o.agentId,sessionId:o.sessionId,pid:o.pid,workspacePath:o.workspacePath,command:o.command,mode:"sdk"})));return}e.info(`[runtime-bridge] \u2605 \u5DF2\u663E\u5F0F\u542F\u7528\u542F\u52A8\u6E05\u7406\uFF0C\u68C0\u6D4B ${a.length} \u4E2A\u6301\u4E45\u5316\u4F1A\u8BDD\u7684\u5B64\u513F\u8FDB\u7A0B...`);const d=await ge(a.map(t=>({agentId:t.agentId,pid:t.pid,workspacePath:t.workspacePath,command:t.command})));if(d.length>0){e.warn(`[runtime-bridge] \u2605 \u68C0\u6D4B\u5230 ${d.length} \u4E2A\u5B64\u513F\u8FDB\u7A0B\u4ECD\u5728\u8FD0\u884C\uFF1A`);for(const t of d)e.warn(` - agentId=${t.agentId}, pid=${t.process.pid}, command=${t.process.command?.slice(0,50)}`);e.info("[runtime-bridge] \u6B63\u5728\u81EA\u52A8\u6E05\u7406\u6240\u6709\u5B64\u513F\u8FDB\u7A0B...");for(const t of d)try{if(t.process.pid)if(process.platform==="win32"){const{execSync:o}=await import("node:child_process");o(`taskkill /PID ${t.process.pid} /T /F`,{encoding:"utf8",timeout:3e3}),e.info(`[runtime-bridge] \u5DF2\u81EA\u52A8\u7EC8\u6B62\u5B64\u513F\u8FDB\u7A0B: agentId=${t.agentId}, PID=${t.process.pid}`)}else process.kill(t.process.pid,"SIGKILL"),e.info(`[runtime-bridge] \u5DF2\u81EA\u52A8\u7EC8\u6B62\u5B64\u513F\u8FDB\u7A0B: agentId=${t.agentId}, PID=${t.process.pid}`)}catch(o){const c=o;e.warn(`[runtime-bridge] \u81EA\u52A8\u7EC8\u6B62\u5B64\u513F\u8FDB\u7A0B\u5931\u8D25: agentId=${t.agentId}, PID=${t.process.pid}, error=${c.message}`)}await new Promise(t=>setTimeout(t,1e3))}await $([]),e.info(`[runtime-bridge] \u2605 \u542F\u52A8\u6E05\u7406\u5B8C\u6210\uFF0C\u5DF2\u6E05\u7A7A\u6240\u6709\u6301\u4E45\u5316\u4F1A\u8BDD\uFF08\u6E05\u7406\u4E86 ${d.length} \u4E2A\u5B64\u513F\u8FDB\u7A0B\uFF09`)}catch(r){const i=r;e.warn("[runtime-bridge] \u542F\u52A8\u65F6\u5B64\u513F\u8FDB\u7A0B\u68C0\u6D4B\u5931\u8D25:",i.message)}}Me().catch(r=>{e.warn("[runtime-bridge] \u5B64\u513F\u8FDB\u7A0B\u68C0\u6D4B\u51FA\u9519:",r)}),pe().catch(r=>{e.warn("[runtime-bridge] \u542F\u52A8\u5B64\u513F\u8FDB\u7A0B\u76D1\u63A7\u5931\u8D25:",r)});const n=I();n.use(D({origin(r,i){if(!r){i(null,!0);return}if(y.includes("*")){i(null,!0);return}if(y.includes(r)){i(null,!0);return}try{if(new URL(r).port===String(h)){i(null,!0);return}}catch{}i(new Error(`[runtime-bridge] CORS origin is not allowed: ${r}`))}})),n.use(I.json({limit:"1mb"})),n.use("/runtime",ae),n.use("/runtime",G),n.use("/runtime",_),n.use("/runtime",F),n.use("/runtime",L),n.use("/runtime",N),n.use("/runtime",j),n.use("/runtime",U),n.use("/runtime",X),n.use("/runtime",ee),n.use("/runtime",ce),n.use("/runtime",W),n.use("/runtime",z),n.use("/runtime",J),n.use("/runtime",Y),n.use("/runtime",q),n.use("/runtime",H),n.use("/runtime",Z),n.use("/runtime",V),n.use("/runtime",re),n.use("/runtime",te),n.use("/pty",oe),n.use("/",ue),n.use("/api/file-browser",Q);function Oe(){const r=n.listen(h,()=>{e.info(`[runtime-bridge] listening on ${h}`)});return ie(r),de(r),r.on("error",i=>{if(i.code==="EADDRINUSE"){e.error(`[runtime-bridge] port ${h} is already in use. Stop the existing runtime-bridge process or set AWS_RUNTIME_BRIDGE_PORT to another port before starting.`),process.exitCode=1;return}e.error("[runtime-bridge] failed to start server:",i),process.exitCode=1}),r}let T=!1;async function b(r,i,s=!1){if(T){e.info(`[runtime-bridge] \u5DF2\u5728\u5173\u95ED\u4E2D\uFF0C\u5FFD\u7565 ${r}`);return}T=!0,s||R()&&(s=!0,e.info("[runtime-bridge] \u2605 \u68C0\u6D4B\u5230 .preserve-sessions \u6807\u8BB0\u6587\u4EF6\uFF0C\u81EA\u52A8\u5347\u7EA7\u4E3A\u4FDD\u7559\u4F1A\u8BDD\u6A21\u5F0F")),e.info(`[runtime-bridge] \u6536\u5230 ${r} \u4FE1\u53F7\uFF0C\u5F00\u59CB\u4F18\u96C5\u5173\u95ED... (preserveSessions=${s})`);const f=Date.now(),u=ve(s);try{const a=S.size;if(u.terminateSdkSessions){e.info(`[runtime-bridge] \u6B63\u5728\u505C\u6B62 ${a} \u4E2A SDK \u4F1A\u8BDD...`);for(const[t,o]of S.entries())try{const c=o.providerId||"claude-code",g=le.get(c);let p="";try{const m=g.getSessionPid?.(t);m&&(p=` (PID: ${m})`)}catch{}await g.terminateSession(t),e.info(`[runtime-bridge] SDK \u4F1A\u8BDD ${t} \u5DF2\u7EC8\u6B62${p}`),S.delete(t)}catch(c){const g=c;e.error(`[runtime-bridge] SDK \u4F1A\u8BDD ${t} \u7EC8\u6B62\u5931\u8D25:`,g.message),S.delete(t)}}else e.info(`[runtime-bridge] \u2605 \u4FDD\u7559 ${a} \u4E2A SDK \u4F1A\u8BDD\uFF0C\u4E0D\u5728\u91CD\u542F\u6062\u590D\u573A\u666F\u4E2D\u7EC8\u6B62 Agent \u8FDB\u7A0B`);const l=se.size;u.closePtySessions?(e.info(`[runtime-bridge] \u6B63\u5728\u505C\u6B62 ${l} \u4E2A PTY \u4F1A\u8BDD...`),ne("shutdown")):e.info(`[runtime-bridge] \u2605 \u4FDD\u7559 ${l} \u4E2A PTY \u4F1A\u8BDD\uFF0C\u4E0D\u5728\u91CD\u542F\u6062\u590D\u573A\u666F\u4E2D\u5173\u95ED\u7EC8\u7AEF\u8FDB\u7A0B`),e.info("[runtime-bridge] \u6B63\u5728\u505C\u6B62\u5B64\u513F\u8FDB\u7A0B\u76D1\u63A7...");try{const c=(await import("file://"+require("path").join(__dirname,"..","package","acode","dist","background-command-manager.js")).catch(()=>({backgroundCommandManager:null}))).backgroundCommandManager;c&&(e.info("[runtime-bridge] \u6B63\u5728\u6E05\u7406\u540E\u53F0\u547D\u4EE4..."),await c.disposeAll())}catch(t){e.warn("[runtime-bridge] \u540E\u53F0\u547D\u4EE4\u6E05\u7406\u5931\u8D25: %s",t?.message)}if(fe(),u.terminateResidualProcesses&&(e.info("[runtime-bridge] \u7B49\u5F85\u8FDB\u7A0B\u5B8C\u5168\u7EC8\u6B62..."),await new Promise(t=>setTimeout(t,1e3))),u.terminateResidualProcesses)try{const{detectOrphanProcesses:t,terminateProcessTree:o}=await import("./services/process-detector.js"),{loadPersistedSessions:c}=await import("./services/terminal-persistence.js"),g=await c();if(g.length>0){const p=await t(g.map(m=>({agentId:m.agentId,pid:m.pid,workspacePath:m.workspacePath,command:m.command})));if(p.length>0){e.warn(`[runtime-bridge] \u68C0\u6D4B\u5230 ${p.length} \u4E2A\u6B8B\u7559\u8FDB\u7A0B\uFF0C\u6B63\u5728\u5F3A\u5236\u7EC8\u6B62...`);for(const m of p)if(m.process.pid){const A=await o(m.process.pid);e.info(`[runtime-bridge] \u5DF2\u5F3A\u5236\u7EC8\u6B62\u6B8B\u7559\u8FDB\u7A0B\u6811\uFF1AagentId=${m.agentId}, PID=${m.process.pid}, \u7EC8\u6B62\u4E86 ${A.terminated} \u4E2A\u8FDB\u7A0B`)}}}}catch(t){const o=t;e.warn("[runtime-bridge] \u6B8B\u7559\u8FDB\u7A0B\u68C0\u67E5\u5931\u8D25:",o.message)}else e.info("[runtime-bridge] \u2605 \u4FDD\u7559\u6A21\u5F0F\u8DF3\u8FC7\u6B8B\u7559\u8FDB\u7A0B\u5F3A\u5236\u7EC8\u6B62\uFF0C\u7B49\u5F85\u8C03\u5EA6\u4E2D\u5FC3\u6062\u590D\u4F1A\u8BDD");if(u.unregisterInstance){e.info("[runtime-bridge] \u6B63\u5728\u6CE8\u9500\u5B9E\u4F8B...");try{await we()}catch(t){const o=t;e.error("[runtime-bridge] \u6CE8\u9500\u5B9E\u4F8B\u5931\u8D25:",o.message)}}else e.info("[runtime-bridge] \u2605 \u4FDD\u7559\u6A21\u5F0F\u8DF3\u8FC7\u5B9E\u4F8B\u6CE8\u9500\uFF0C\u907F\u514D\u8C03\u5EA6\u4E2D\u5FC3\u5C06 Agent \u6807\u8BB0\u4E3A stopped");if(Se(),u.stopTunnelClient&&(e.info("[runtime-bridge] \u6B63\u5728\u5173\u95ED\u96A7\u9053\u5BA2\u6237\u7AEF..."),Re()),u.clearPersistedSessions){e.info("[runtime-bridge] \u6B63\u5728\u6E05\u7A7A\u6301\u4E45\u5316\u4F1A\u8BDD\u72B6\u6001...");try{await $([])}catch(t){const o=t;e.error("[runtime-bridge] \u6E05\u7A7A\u6301\u4E45\u5316\u72B6\u6001\u5931\u8D25:",o.message)}}else e.info("[runtime-bridge] \u2605 \u4FDD\u7559\u6301\u4E45\u5316\u4F1A\u8BDD\u72B6\u6001\uFF08aws-mcp-server \u91CD\u542F\u6062\u590D\u573A\u666F\uFF09");try{await Te(),e.info("[runtime-bridge] browser \u5DE5\u5177\u6808\u5DF2\u6E05\u7406")}catch(t){e.warn("[runtime-bridge] browser \u5DE5\u5177\u6808\u6E05\u7406\u5931\u8D25:",t.message)}const d=Date.now()-f;e.info(`[runtime-bridge] \u4F18\u96C5\u5173\u95ED\u5B8C\u6210 (\u8017\u65F6 ${d}ms)\uFF0C\u505C\u6B62 ${a} SDK \u4F1A\u8BDD`),i.close(()=>{e.info("[runtime-bridge] HTTP \u670D\u52A1\u5668\u5DF2\u5173\u95ED"),process.exit(process.exitCode||0)}),setTimeout(()=>{e.warn("[runtime-bridge] \u4F18\u96C5\u5173\u95ED\u8D85\u65F6\uFF0C\u5F3A\u5236\u9000\u51FA"),process.exit(1)},15e3)}catch(a){const l=a;e.error("[runtime-bridge] \u4F18\u96C5\u5173\u95ED\u8FC7\u7A0B\u4E2D\u53D1\u751F\u9519\u8BEF:",l),process.exit(1)}}Ce.reconcileZombies().then(r=>{r>0&&e.info(`[runtime-bridge] \u5DF2\u6E05\u7406 ${r} \u4E2A\u50F5\u5C38 sub-agent \u8BB0\u5F55`)}).catch(r=>{e.warn("[runtime-bridge] \u6E05\u7406\u50F5\u5C38 sub-agent \u8BB0\u5F55\u5931\u8D25:",r.message)});const w=Oe(),v=x.join(M.tmpdir(),"agentswork-runtime-bridge",".preserve-sessions");function R(){try{if(E(v)){const r=B(v,"utf-8");return JSON.parse(r).preserveSessions===!0}}catch{}return!1}K(async(r,i)=>{await b(r,w,i)}),process.on("SIGTERM",()=>{const r=R();b("SIGTERM",w,r)}),process.on("SIGINT",()=>{const r=R();b("SIGINT",w,r)}),process.platform==="win32"&&process.on("SIGBREAK",()=>{const r=R();b("SIGBREAK",w,r)}),process.on("uncaughtException",r=>{e.error("[runtime-bridge] \u672A\u6355\u83B7\u5F02\u5E38:",r),b("uncaughtException",w)}),process.on("unhandledRejection",r=>{e.error("[runtime-bridge] \u672A\u5904\u7406\u7684 Promise \u62D2\u7EDD:",r)});
|
|
2
|
+
import I from"express";import B from"cors";import{existsSync as D,readFileSync as E}from"node:fs";import x from"node:path";import M from"node:os";import{allowedCorsOrigins as y,port as h,validateProductionToken as O}from"./config.js";import{propertiesRouter as G}from"./routes/properties.js";import{ymlRouter as _}from"./routes/yml.js";import{instanceRouter as F,setGracefulShutdownFn as K}from"./routes/instance.js";import{skillsRouter as L}from"./routes/skills.js";import{mcpRouter as N}from"./routes/mcp.js";import{terminalRouter as j,sdkSessions as S}from"./routes/terminal.js";import{sessionsRouter as W}from"./routes/sessions.js";import{gitRouter as U}from"./routes/git.js";import{fileSnapshotRouter as z}from"./routes/file-snapshot.js";import{acodeTodosRouter as J}from"./routes/acode-todos.js";import{backgroundTasksRouter as Y}from"./routes/background-tasks.js";import{subAgentsRouter as q}from"./routes/sub-agents.js";import{browserSessionsRouter as H}from"./routes/browser-sessions.js";import{browserListenersRouter as Z}from"./routes/browser-listeners.js";import{fileBrowserRouter as Q}from"./routes/file-browser.js";import{aiSourcesRouter as V}from"./routes/ai-sources.js";import{processesRouter as X}from"./routes/processes.js";import{systemMetricsRouter as ee}from"./routes/system-metrics.js";import{taskContextRouter as re}from"./routes/task-context.js";import{fileDiffRollbackRouter as te}from"./routes/file-diff-rollback.js";import{attachPtyWebSocketServer as ie,closeAllPtySessions as ne,ptyRouter as oe,ptySessions as se}from"./routes/pty.js";import{runtimeBindingRouter as ae,logRuntimeBindingStartupState as me}from"./routes/runtime-binding.js";import{dashboardRouter as ue}from"./routes/dashboard.js";import{eventsRouter as ce}from"./routes/events.js";import{attachRemoteDesktopWebSocketServer as de}from"./remote-desktop/index.js";import{attachBrowserWebSocketServer as ge}from"./browser/browser-ws-bridge.js";import{loadPersistedSessions as P,savePersistedSessions as $}from"./services/terminal-persistence.js";import{detectOrphanProcesses as pe}from"./services/process-detector.js";import{startOrphanMonitor as fe,stopOrphanMonitor as le}from"./services/orphan-monitor.js";import{getAgentProcessManager as k}from"./services/agent-process-manager.js";import{adapterRegistry as be}from"./adapter/index.js";import{logger as e}from"./utils/logger.js";import{autoRegister as we,unregister as he,bridgeRestartCleanup as Se,cancelBackgroundRegisterRetries as Re}from"./services/auto-register.js";import{stopTunnelClient as Ie}from"./services/tunnel-client.js";import{ensureAwsClientAgentMcpReleased as ye}from"./services/aws-client-agent-mcp.js";import{ensureAcodePackageReleased as Pe}from"./services/acode-package.js";import{ensureStartupConfig as $e}from"./services/startup-config-wizard.js";import{handleCliCommand as ke}from"./services/cli-commands.js";import{migrateOldPathsOnStartup as Ce}from"./services/legacy-path-migration.js";import{subAgentStore as Te}from"./services/sub-agent-manager-instance.js";import{disposeBrowserSessionManager as ve}from"./services/browser-session-manager.js";import{buildGracefulShutdownPlan as Ae,shouldCleanupPersistedSessionsOnStartup as Be}from"./services/runtime-lifecycle-policy.js";import{deliverPendingBridgeLifecycleNotifications as De}from"./services/lifecycle-state.js";const C=await ke();C.handled&&process.exit(C.exitCode),await $e(),O(),ye(),Pe(),Ce(),me();async function Ee(){try{if(await we())e.info("[runtime-bridge] \u81EA\u52A8\u6CE8\u518C\u6210\u529F");else{const{loadConfig:i}=await import("./services/auto-register.js"),s=i();!!s.userKey&&s.enabled?e.warn("[runtime-bridge] \u81EA\u52A8\u6CE8\u518C\u6682\u65F6\u5931\u8D25\uFF0C\u5C06\u5728\u540E\u53F0\u6301\u7EED\u91CD\u8BD5\u76F4\u5230\u4E0E\u8C03\u5EA6\u4E2D\u5FC3\u5EFA\u7ACB\u8FDE\u63A5"):(e.info("[runtime-bridge] \u81EA\u52A8\u6CE8\u518C\u672A\u914D\u7F6E\uFF0C\u7EE7\u7EED\u542F\u52A8\u670D\u52A1"),e.info("[runtime-bridge] \u8981\u542F\u7528\u81EA\u52A8\u6CE8\u518C\uFF0C\u8BF7\u521B\u5EFA\u914D\u7F6E\u6587\u4EF6 ~/.aws-bridge/config.json"))}}catch(r){const i=r;e.warn("[runtime-bridge] \u81EA\u52A8\u6CE8\u518C\u51FA\u9519:",i.message)}}Ee().then(()=>xe()).then(()=>De()).then(()=>{e.info("[runtime-bridge] Bridge \u751F\u547D\u5468\u671F\u901A\u77E5\u540C\u6B65\u5B8C\u6210")}).catch(r=>{e.warn("[runtime-bridge] \u81EA\u52A8\u6CE8\u518C\u6216\u6E05\u7406\u51FA\u9519:",r)});async function xe(){try{if(process.env.AWS_BRIDGE_RESTART_CLEANUP!=="true"){e.info("[runtime-bridge] \u8DF3\u8FC7 Bridge \u91CD\u542F\u6E05\u7406\uFF0C\u4FDD\u7559 Agent \u72B6\u6001\u4F9B\u8C03\u5EA6\u4E2D\u5FC3\u6062\u590D");return}const{getRegistrationState:r}=await import("./services/auto-register.js"),i=r();if(!i.registered||!i.instanceId){e.info("[runtime-bridge] \u5B9E\u4F8B\u672A\u6CE8\u518C\uFF0C\u8DF3\u8FC7 Bridge \u91CD\u542F\u6E05\u7406");return}e.info("[runtime-bridge] \u2605 Bridge \u91CD\u542F\u6E05\u7406\u5DF2\u663E\u5F0F\u542F\u7528\uFF0C\u6B63\u5728\u901A\u77E5\u8C03\u5EA6\u4E2D\u5FC3\u6E05\u7406 Agent \u72B6\u6001...");const s=await Se();s.success?e.info(`[runtime-bridge] \u2605 Bridge \u91CD\u542F\u6E05\u7406\u6210\u529F\uFF0C\u6E05\u7406\u4E86 ${s.agentCount||0} \u4E2A Agent`):e.warn(`[runtime-bridge] \u2605 Bridge \u91CD\u542F\u6E05\u7406\u5931\u8D25: ${s.error}`)}catch(r){const i=r;e.warn("[runtime-bridge] Bridge \u91CD\u542F\u6E05\u7406\u51FA\u9519:",i.message)}}async function Me(){try{const r=await P();if(r.length===0){e.info("[runtime-bridge] \u65E0\u6301\u4E45\u5316\u4F1A\u8BDD\uFF0C\u8DF3\u8FC7\u8FDB\u7A0B\u6CE8\u518C\u8868\u91CD\u5EFA");return}await k().rebuildRegistry(r.map(s=>({agentId:s.agentId,sessionId:s.sessionId,pid:s.pid,workspacePath:s.workspacePath,command:s.command,mode:"sdk"}))),e.info(`[runtime-bridge] \u8FDB\u7A0B\u6CE8\u518C\u8868\u91CD\u5EFA\u5B8C\u6210: ${r.length} \u6761\u8BB0\u5F55`)}catch(r){const i=r;e.warn("[runtime-bridge] \u8FDB\u7A0B\u6CE8\u518C\u8868\u91CD\u5EFA\u5931\u8D25:",i.message)}}Me().catch(r=>{e.warn("[runtime-bridge] \u8FDB\u7A0B\u6CE8\u518C\u8868\u91CD\u5EFA\u51FA\u9519:",r)});async function Oe(){try{const r=await import("node:fs/promises"),i=await import("node:path"),s=await import("node:os"),f=i.join(s.tmpdir(),"agentswork-runtime-bridge",".preserve-sessions");let u=!1;try{const t=await r.readFile(f,"utf-8");u=JSON.parse(t).preserveSessions===!0,await r.unlink(f),e.info(`[runtime-bridge] \u2605 \u68C0\u6D4B\u5230\u4FDD\u7559\u4F1A\u8BDD\u6807\u8BB0\uFF0CpreserveSessions=${u}`)}catch{}const a=await P();if(a.length===0){e.info("[runtime-bridge] \u542F\u52A8\u65F6\u65E0\u6301\u4E45\u5316\u4F1A\u8BDD\uFF0C\u65E0\u9700\u68C0\u6D4B\u5B64\u513F\u8FDB\u7A0B");return}if(!Be(u)){e.info(`[runtime-bridge] \u2605 \u4FDD\u7559 ${a.length} \u4E2A\u6301\u4E45\u5316\u4F1A\u8BDD\u5E76\u91CD\u5EFA\u8FDB\u7A0B\u6CE8\u518C\u8868`),await k().rebuildRegistry(a.map(o=>({agentId:o.agentId,sessionId:o.sessionId,pid:o.pid,workspacePath:o.workspacePath,command:o.command,mode:"sdk"})));return}e.info(`[runtime-bridge] \u2605 \u5DF2\u663E\u5F0F\u542F\u7528\u542F\u52A8\u6E05\u7406\uFF0C\u68C0\u6D4B ${a.length} \u4E2A\u6301\u4E45\u5316\u4F1A\u8BDD\u7684\u5B64\u513F\u8FDB\u7A0B...`);const d=await pe(a.map(t=>({agentId:t.agentId,pid:t.pid,workspacePath:t.workspacePath,command:t.command})));if(d.length>0){e.warn(`[runtime-bridge] \u2605 \u68C0\u6D4B\u5230 ${d.length} \u4E2A\u5B64\u513F\u8FDB\u7A0B\u4ECD\u5728\u8FD0\u884C\uFF1A`);for(const t of d)e.warn(` - agentId=${t.agentId}, pid=${t.process.pid}, command=${t.process.command?.slice(0,50)}`);e.info("[runtime-bridge] \u6B63\u5728\u81EA\u52A8\u6E05\u7406\u6240\u6709\u5B64\u513F\u8FDB\u7A0B...");for(const t of d)try{if(t.process.pid)if(process.platform==="win32"){const{execSync:o}=await import("node:child_process");o(`taskkill /PID ${t.process.pid} /T /F`,{encoding:"utf8",timeout:3e3}),e.info(`[runtime-bridge] \u5DF2\u81EA\u52A8\u7EC8\u6B62\u5B64\u513F\u8FDB\u7A0B: agentId=${t.agentId}, PID=${t.process.pid}`)}else process.kill(t.process.pid,"SIGKILL"),e.info(`[runtime-bridge] \u5DF2\u81EA\u52A8\u7EC8\u6B62\u5B64\u513F\u8FDB\u7A0B: agentId=${t.agentId}, PID=${t.process.pid}`)}catch(o){const c=o;e.warn(`[runtime-bridge] \u81EA\u52A8\u7EC8\u6B62\u5B64\u513F\u8FDB\u7A0B\u5931\u8D25: agentId=${t.agentId}, PID=${t.process.pid}, error=${c.message}`)}await new Promise(t=>setTimeout(t,1e3))}await $([]),e.info(`[runtime-bridge] \u2605 \u542F\u52A8\u6E05\u7406\u5B8C\u6210\uFF0C\u5DF2\u6E05\u7A7A\u6240\u6709\u6301\u4E45\u5316\u4F1A\u8BDD\uFF08\u6E05\u7406\u4E86 ${d.length} \u4E2A\u5B64\u513F\u8FDB\u7A0B\uFF09`)}catch(r){const i=r;e.warn("[runtime-bridge] \u542F\u52A8\u65F6\u5B64\u513F\u8FDB\u7A0B\u68C0\u6D4B\u5931\u8D25:",i.message)}}Oe().catch(r=>{e.warn("[runtime-bridge] \u5B64\u513F\u8FDB\u7A0B\u68C0\u6D4B\u51FA\u9519:",r)}),fe().catch(r=>{e.warn("[runtime-bridge] \u542F\u52A8\u5B64\u513F\u8FDB\u7A0B\u76D1\u63A7\u5931\u8D25:",r)});const n=I();n.use(B({origin(r,i){if(!r){i(null,!0);return}if(y.includes("*")){i(null,!0);return}if(y.includes(r)){i(null,!0);return}try{if(new URL(r).port===String(h)){i(null,!0);return}}catch{}i(new Error(`[runtime-bridge] CORS origin is not allowed: ${r}`))}})),n.use(I.json({limit:"1mb"})),n.use("/runtime",ae),n.use("/runtime",G),n.use("/runtime",_),n.use("/runtime",F),n.use("/runtime",L),n.use("/runtime",N),n.use("/runtime",j),n.use("/runtime",W),n.use("/runtime",X),n.use("/runtime",ee),n.use("/runtime",ce),n.use("/runtime",U),n.use("/runtime",z),n.use("/runtime",J),n.use("/runtime",Y),n.use("/runtime",q),n.use("/runtime",H),n.use("/runtime",Z),n.use("/runtime",V),n.use("/runtime",re),n.use("/runtime",te),n.use("/pty",oe),n.use("/",ue),n.use("/api/file-browser",Q);function Ge(){const r=n.listen(h,()=>{e.info(`[runtime-bridge] listening on ${h}`)});return ie(r),de(r),ge(r),r.on("error",i=>{if(i.code==="EADDRINUSE"){e.error(`[runtime-bridge] port ${h} is already in use. Stop the existing runtime-bridge process or set AWS_RUNTIME_BRIDGE_PORT to another port before starting.`),process.exitCode=1;return}e.error("[runtime-bridge] failed to start server:",i),process.exitCode=1}),r}let T=!1;async function b(r,i,s=!1){if(T){e.info(`[runtime-bridge] \u5DF2\u5728\u5173\u95ED\u4E2D\uFF0C\u5FFD\u7565 ${r}`);return}T=!0,s||R()&&(s=!0,e.info("[runtime-bridge] \u2605 \u68C0\u6D4B\u5230 .preserve-sessions \u6807\u8BB0\u6587\u4EF6\uFF0C\u81EA\u52A8\u5347\u7EA7\u4E3A\u4FDD\u7559\u4F1A\u8BDD\u6A21\u5F0F")),e.info(`[runtime-bridge] \u6536\u5230 ${r} \u4FE1\u53F7\uFF0C\u5F00\u59CB\u4F18\u96C5\u5173\u95ED... (preserveSessions=${s})`);const f=Date.now(),u=Ae(s);try{const a=S.size;if(u.terminateSdkSessions){e.info(`[runtime-bridge] \u6B63\u5728\u505C\u6B62 ${a} \u4E2A SDK \u4F1A\u8BDD...`);for(const[t,o]of S.entries())try{const c=o.providerId||"claude-code",g=be.get(c);let p="";try{const m=g.getSessionPid?.(t);m&&(p=` (PID: ${m})`)}catch{}await g.terminateSession(t),e.info(`[runtime-bridge] SDK \u4F1A\u8BDD ${t} \u5DF2\u7EC8\u6B62${p}`),S.delete(t)}catch(c){const g=c;e.error(`[runtime-bridge] SDK \u4F1A\u8BDD ${t} \u7EC8\u6B62\u5931\u8D25:`,g.message),S.delete(t)}}else e.info(`[runtime-bridge] \u2605 \u4FDD\u7559 ${a} \u4E2A SDK \u4F1A\u8BDD\uFF0C\u4E0D\u5728\u91CD\u542F\u6062\u590D\u573A\u666F\u4E2D\u7EC8\u6B62 Agent \u8FDB\u7A0B`);const l=se.size;u.closePtySessions?(e.info(`[runtime-bridge] \u6B63\u5728\u505C\u6B62 ${l} \u4E2A PTY \u4F1A\u8BDD...`),ne("shutdown")):e.info(`[runtime-bridge] \u2605 \u4FDD\u7559 ${l} \u4E2A PTY \u4F1A\u8BDD\uFF0C\u4E0D\u5728\u91CD\u542F\u6062\u590D\u573A\u666F\u4E2D\u5173\u95ED\u7EC8\u7AEF\u8FDB\u7A0B`),e.info("[runtime-bridge] \u6B63\u5728\u505C\u6B62\u5B64\u513F\u8FDB\u7A0B\u76D1\u63A7...");try{const c=(await import("file://"+require("path").join(__dirname,"..","package","acode","dist","background-command-manager.js")).catch(()=>({backgroundCommandManager:null}))).backgroundCommandManager;c&&(e.info("[runtime-bridge] \u6B63\u5728\u6E05\u7406\u540E\u53F0\u547D\u4EE4..."),await c.disposeAll())}catch(t){e.warn("[runtime-bridge] \u540E\u53F0\u547D\u4EE4\u6E05\u7406\u5931\u8D25: %s",t?.message)}if(le(),u.terminateResidualProcesses&&(e.info("[runtime-bridge] \u7B49\u5F85\u8FDB\u7A0B\u5B8C\u5168\u7EC8\u6B62..."),await new Promise(t=>setTimeout(t,1e3))),u.terminateResidualProcesses)try{const{detectOrphanProcesses:t,terminateProcessTree:o}=await import("./services/process-detector.js"),{loadPersistedSessions:c}=await import("./services/terminal-persistence.js"),g=await c();if(g.length>0){const p=await t(g.map(m=>({agentId:m.agentId,pid:m.pid,workspacePath:m.workspacePath,command:m.command})));if(p.length>0){e.warn(`[runtime-bridge] \u68C0\u6D4B\u5230 ${p.length} \u4E2A\u6B8B\u7559\u8FDB\u7A0B\uFF0C\u6B63\u5728\u5F3A\u5236\u7EC8\u6B62...`);for(const m of p)if(m.process.pid){const A=await o(m.process.pid);e.info(`[runtime-bridge] \u5DF2\u5F3A\u5236\u7EC8\u6B62\u6B8B\u7559\u8FDB\u7A0B\u6811\uFF1AagentId=${m.agentId}, PID=${m.process.pid}, \u7EC8\u6B62\u4E86 ${A.terminated} \u4E2A\u8FDB\u7A0B`)}}}}catch(t){const o=t;e.warn("[runtime-bridge] \u6B8B\u7559\u8FDB\u7A0B\u68C0\u67E5\u5931\u8D25:",o.message)}else e.info("[runtime-bridge] \u2605 \u4FDD\u7559\u6A21\u5F0F\u8DF3\u8FC7\u6B8B\u7559\u8FDB\u7A0B\u5F3A\u5236\u7EC8\u6B62\uFF0C\u7B49\u5F85\u8C03\u5EA6\u4E2D\u5FC3\u6062\u590D\u4F1A\u8BDD");if(u.unregisterInstance){e.info("[runtime-bridge] \u6B63\u5728\u6CE8\u9500\u5B9E\u4F8B...");try{await he()}catch(t){const o=t;e.error("[runtime-bridge] \u6CE8\u9500\u5B9E\u4F8B\u5931\u8D25:",o.message)}}else e.info("[runtime-bridge] \u2605 \u4FDD\u7559\u6A21\u5F0F\u8DF3\u8FC7\u5B9E\u4F8B\u6CE8\u9500\uFF0C\u907F\u514D\u8C03\u5EA6\u4E2D\u5FC3\u5C06 Agent \u6807\u8BB0\u4E3A stopped");if(Re(),u.stopTunnelClient&&(e.info("[runtime-bridge] \u6B63\u5728\u5173\u95ED\u96A7\u9053\u5BA2\u6237\u7AEF..."),Ie()),u.clearPersistedSessions){e.info("[runtime-bridge] \u6B63\u5728\u6E05\u7A7A\u6301\u4E45\u5316\u4F1A\u8BDD\u72B6\u6001...");try{await $([])}catch(t){const o=t;e.error("[runtime-bridge] \u6E05\u7A7A\u6301\u4E45\u5316\u72B6\u6001\u5931\u8D25:",o.message)}}else e.info("[runtime-bridge] \u2605 \u4FDD\u7559\u6301\u4E45\u5316\u4F1A\u8BDD\u72B6\u6001\uFF08aws-mcp-server \u91CD\u542F\u6062\u590D\u573A\u666F\uFF09");try{await ve(),e.info("[runtime-bridge] browser \u5DE5\u5177\u6808\u5DF2\u6E05\u7406")}catch(t){e.warn("[runtime-bridge] browser \u5DE5\u5177\u6808\u6E05\u7406\u5931\u8D25:",t.message)}const d=Date.now()-f;e.info(`[runtime-bridge] \u4F18\u96C5\u5173\u95ED\u5B8C\u6210 (\u8017\u65F6 ${d}ms)\uFF0C\u505C\u6B62 ${a} SDK \u4F1A\u8BDD`),i.close(()=>{e.info("[runtime-bridge] HTTP \u670D\u52A1\u5668\u5DF2\u5173\u95ED"),process.exit(process.exitCode||0)}),setTimeout(()=>{e.warn("[runtime-bridge] \u4F18\u96C5\u5173\u95ED\u8D85\u65F6\uFF0C\u5F3A\u5236\u9000\u51FA"),process.exit(1)},15e3)}catch(a){const l=a;e.error("[runtime-bridge] \u4F18\u96C5\u5173\u95ED\u8FC7\u7A0B\u4E2D\u53D1\u751F\u9519\u8BEF:",l),process.exit(1)}}Te.reconcileZombies().then(r=>{r>0&&e.info(`[runtime-bridge] \u5DF2\u6E05\u7406 ${r} \u4E2A\u50F5\u5C38 sub-agent \u8BB0\u5F55`)}).catch(r=>{e.warn("[runtime-bridge] \u6E05\u7406\u50F5\u5C38 sub-agent \u8BB0\u5F55\u5931\u8D25:",r.message)});const w=Ge(),v=x.join(M.tmpdir(),"agentswork-runtime-bridge",".preserve-sessions");function R(){try{if(D(v)){const r=E(v,"utf-8");return JSON.parse(r).preserveSessions===!0}}catch{}return!1}K(async(r,i)=>{await b(r,w,i)}),process.on("SIGTERM",()=>{const r=R();b("SIGTERM",w,r)}),process.on("SIGINT",()=>{const r=R();b("SIGINT",w,r)}),process.platform==="win32"&&process.on("SIGBREAK",()=>{const r=R();b("SIGBREAK",w,r)}),process.on("uncaughtException",r=>{e.error("[runtime-bridge] \u672A\u6355\u83B7\u5F02\u5E38:",r),b("uncaughtException",w)}),process.on("unhandledRejection",r=>{e.error("[runtime-bridge] \u672A\u5904\u7406\u7684 Promise \u62D2\u7EDD:",r)});
|
|
3
3
|
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{UIAutomation as i}from"./ui-automation.js";class c{constructor(t,e){this.screenCapture=t,this.inputBackend=e,this.uiAutomation=new i}async screenshot(t){
|
|
1
|
+
import{UIAutomation as i}from"./ui-automation.js";class c{constructor(t,e){this.screenCapture=t,this.inputBackend=e,this.uiAutomation=new i}async screenshot(t){const e=await this.screenCapture.captureVirtualDesktop(!0);if(!e||e.kind!=="full")throw new Error("screenshot failed: no frame captured");return{data:e.data,width:e.width,height:e.height}}async moveMouse(t,e){await this.inputBackend.moveMouse(t,e)}async click(t,e,n="left"){await this.inputBackend.click(t,e,n)}async doubleClick(t,e){await this.inputBackend.doubleClick(t,e)}async mouseDown(t="left"){await this.inputBackend.mouseDown(t)}async mouseUp(t="left"){await this.inputBackend.mouseUp(t)}async scroll(t,e){await this.inputBackend.scroll(t,e)}async type(t){await this.inputBackend.type(t)}async keyPress(t){await this.inputBackend.keyPress(t)}async keyDown(t){await this.inputBackend.keyDown(t)}async keyUp(t){await this.inputBackend.keyUp(t)}async listScreens(){return this.screenCapture.listScreens()}getScreenSize(){return this.inputBackend.getScreenSize()}async listApplications(){return this.uiAutomation.listApplications()}async getUIElements(t){return this.uiAutomation.getUIElements(t)}async clickElement(t){return this.uiAutomation.clickElement(t)}async setElementValue(t,e){return this.uiAutomation.setElementValue(t,e)}}export{c as ComputerControl};
|
|
2
2
|
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
const r=21060,U=2,B=1,E=2;function w(e,c){const i=c>>>0;if(e.kind==="full"){const t=Buffer.allocUnsafe(16);return t.writeUInt16BE(21060,0),t[2]=2,t[3]=2,t.writeUInt32BE(i,4),t.writeUInt32BE(e.width,8),t.writeUInt32BE(e.height,12),Buffer.concat([t,e.data])}const s=e.tiles.length,l=10+s*12;let I=0;for(const t of e.tiles)I+=t.data.length;const n=Buffer.allocUnsafe(l+I);n.writeUInt16BE(21060,0),n[2]=2,n[3]=1,n.writeUInt32BE(i,4),n.writeUInt16BE(s,8);let o=10;for(const t of e.tiles)n.writeUInt16BE(t.x,o),n.writeUInt16BE(t.y,o+2),n.writeUInt16BE(t.w,o+4),n.writeUInt16BE(t.h,o+6),n.writeUInt32BE(t.data.length,o+8),o+=12;for(const t of e.tiles)t.data.copy(n,o),o+=t.data.length;return n}export{w as encodeFrame};
|
|
2
|
+
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{WebSocketServer as
|
|
1
|
+
import{WebSocketServer as C}from"ws";import{SessionManager as F}from"./session-manager.js";import{PermissionManager as D}from"./permission-manager.js";import{encodeFrame as M}from"./frame-encode.js";import{getSharpLoadStatus as T}from"./screen-capture.js";import{isRuntimeTokenValid as v}from"../middleware/auth.js";import{getFrameBufferLimit as A,shouldDropFrameForBackpressure as I}from"./frame-backpressure.js";const h=new D,i=new F(h);function P(n,o){const t=o.searchParams.get("token");if(t)return t;const e=n.headers["x-runtime-token"];if(typeof e=="string")return e;const s=n.headers.authorization;return typeof s=="string"&&s.startsWith("Bearer ")?s.slice(7):""}function l(n,o){n.readyState===n.OPEN&&n.send(JSON.stringify(o))}function W(n,o,t){switch(t.type){case"remote-desktop:input":{const e=t.data;i.handleInput(o,e).catch(s=>{l(n,{type:"remote-desktop:error",sessionId:o,data:{error:String(s)}})});break}case"remote-desktop:stop":{i.closeSession(o),n.close();break}case"remote-desktop:quality":{const e=i.getSession(o);e&&t.data&&e.screenCapture.setQuality(t.data.quality,t.data.fps);break}case"remote-desktop:control-mode":{const e=i.getSession(o);e&&t.data&&(t.data.enabled?e.enableControl().catch(s=>{console.warn("[remote-desktop] enableControl failed:",s)}):e.stopInputBackend());break}case"remote-desktop:switch-screen":{const e=i.getSession(o);e&&t.data&&e.switchScreen(t.data.screenId||null).catch(s=>{l(n,{type:"remote-desktop:error",sessionId:o,data:{error:String(s)}})});break}case"remote-desktop:ping":{l(n,{type:"remote-desktop:pong",sessionId:o,data:t.data});break}case"remote-desktop:resync":{const e=i.getSession(o);e&&e.screenCapture.requestFullFrame();break}}}function J(n){const o=new C({noServer:!0});n.on("upgrade",(t,e,s)=>{const k=new URL(t.url||"/",`http://${t.headers.host}`);if(k.pathname!=="/remote-desktop")return;const S=P(t,k);if(!v(S)&&!h.validateToken(S)){e.write(`HTTP/1.1 401 Unauthorized\r
|
|
2
2
|
\r
|
|
3
|
-
`),
|
|
3
|
+
`),e.destroy();return}o.handleUpgrade(t,e,s,r=>{const b=k.searchParams.get("agentId")||"default";i.createSession(b,S).then(a=>{const c=i.getSession(a.sessionId);if(c?.attachWebSocket(r),l(r,{type:"remote-desktop:start-result",sessionId:a.sessionId,data:{screens:a.screens,permissions:a.permissions}}),c){let d=0,p=0,m=Date.now();const $=setInterval(()=>{const u=Date.now()-m,f=c.screenCapture.frameStats;console.log(`[remote-desktop] frames: sent=${d} dropped=${p} bufferedAmount=${r.bufferedAmount} silent=${u}ms quality=${c.screenCapture.getQuality()} sharp=${T()} full=${f.full} partial=${f.partial} null=${f.null} sharpFail=${f.sharpFail}`),d=0,p=0,c.screenCapture.resetFrameStats()},5e3);let g=0;c.startFrameLoop(y=>{if(r.readyState===r.OPEN&&!I(r.bufferedAmount,A(c.screenCapture.getQuality())))return r.send(M(y,g)),g=g+1>>>0,d++,m=Date.now(),!0;p++,c.screenCapture.notifyFrameDropped();const u=Date.now()-m;return u>3e3&&console.warn(`[remote-desktop] \u8FDE\u7EED ${u}ms \u65E0\u5E27\u53D1\u9001 bufferedAmount=${r.bufferedAmount} readyState=${r.readyState}`),!1}),r.on("close",()=>clearInterval($))}r.on("message",d=>{try{const p=d.toString();if(p.startsWith("{")){const m=JSON.parse(p);W(r,a.sessionId,m)}}catch{}}),r.on("close",()=>{i.closeSession(a.sessionId)}),r.on("error",()=>{i.closeSession(a.sessionId)})}).catch(a=>{console.error("[remote-desktop] createSession failed:",a),l(r,{type:"remote-desktop:error",sessionId:"",data:{error:String(a)}}),r.close(1011,String(a))})})})}export{J as attachRemoteDesktopWebSocketServer,h as permissionManager,i as sessionManager};
|
|
4
4
|
|
|
@@ -1,24 +1,99 @@
|
|
|
1
|
-
import type { RemoteDesktopQuality, ScreenInfo,
|
|
1
|
+
import type { RemoteDesktopQuality, ScreenInfo, CaptureFrame } from "./types.js";
|
|
2
|
+
|
|
3
|
+
export declare function getSharpLoadStatus(): string;
|
|
4
|
+
|
|
5
|
+
export declare function extractRawRegion(raw: Buffer, srcWidth: number, left: number, top: number, w: number, h: number): Buffer | null;
|
|
2
6
|
export declare class ScreenCapture {
|
|
7
|
+
|
|
8
|
+
private static readonly ACTIVE_INTERVAL_MS;
|
|
9
|
+
|
|
10
|
+
private static readonly STILL_FRAME_COUNT;
|
|
11
|
+
|
|
12
|
+
private static readonly IDLE_INTERVAL_MS;
|
|
13
|
+
|
|
14
|
+
private static readonly PERIODIC_FULL_FRAME_INTERVAL_MS;
|
|
3
15
|
private screens;
|
|
4
|
-
|
|
16
|
+
|
|
17
|
+
private captureTimer;
|
|
5
18
|
|
|
6
19
|
private fps;
|
|
7
20
|
|
|
8
21
|
private quality;
|
|
9
22
|
private activeScreenId;
|
|
23
|
+
|
|
10
24
|
private onFrame;
|
|
11
25
|
|
|
12
|
-
private
|
|
26
|
+
private captureInFlight;
|
|
27
|
+
|
|
28
|
+
private encodePending;
|
|
29
|
+
|
|
30
|
+
private encoding;
|
|
31
|
+
|
|
32
|
+
private consecutiveStillFrames;
|
|
33
|
+
|
|
34
|
+
private inputBoostFrames;
|
|
35
|
+
|
|
36
|
+
private lastRaw;
|
|
37
|
+
private lastRawWidth;
|
|
38
|
+
private lastRawHeight;
|
|
39
|
+
|
|
40
|
+
private pendingReference;
|
|
41
|
+
frameStats: {
|
|
42
|
+
full: number;
|
|
43
|
+
partial: number;
|
|
44
|
+
null: number;
|
|
45
|
+
sharpFail: number;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
resetFrameStats(): void;
|
|
13
49
|
listScreens(): Promise<ScreenInfo[]>;
|
|
14
50
|
|
|
15
|
-
|
|
51
|
+
private cachedMonitors;
|
|
52
|
+
private getActiveMonitor;
|
|
53
|
+
|
|
54
|
+
private acquireShot;
|
|
55
|
+
|
|
56
|
+
private encodeShot;
|
|
57
|
+
|
|
58
|
+
captureVirtualDesktop(force?: boolean): Promise<CaptureFrame | null>;
|
|
59
|
+
|
|
60
|
+
private totalTileCount;
|
|
61
|
+
|
|
62
|
+
private mapLimit;
|
|
63
|
+
|
|
64
|
+
private outputRectToSource;
|
|
65
|
+
|
|
66
|
+
private detectChangedTiles;
|
|
67
|
+
|
|
68
|
+
private commitReferenceFrame;
|
|
69
|
+
|
|
70
|
+
private discardReferenceFrame;
|
|
16
71
|
|
|
17
72
|
setActiveScreen(screenId: string | null): void;
|
|
18
73
|
|
|
19
|
-
startLoop(onFrame: (frame:
|
|
74
|
+
startLoop(onFrame: (frame: CaptureFrame) => boolean | void): void;
|
|
20
75
|
|
|
21
76
|
private scheduleLoop;
|
|
77
|
+
|
|
78
|
+
private lastFullFrameAt;
|
|
79
|
+
|
|
80
|
+
private captureOnce;
|
|
81
|
+
|
|
82
|
+
private drainEncode;
|
|
83
|
+
|
|
84
|
+
private consecutiveNullCaptures;
|
|
85
|
+
|
|
86
|
+
private captureSafeFullFrame;
|
|
87
|
+
|
|
88
|
+
private currentIntervalMs;
|
|
89
|
+
|
|
90
|
+
requestFrame(): void;
|
|
91
|
+
|
|
92
|
+
private backpressureThrottleUntil;
|
|
93
|
+
|
|
94
|
+
notifyFrameDropped(): void;
|
|
95
|
+
|
|
96
|
+
requestFullFrame(): void;
|
|
22
97
|
stopLoop(): void;
|
|
23
98
|
|
|
24
99
|
setQuality(quality?: RemoteDesktopQuality, fps?: number): void;
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
let o=null;async function S(){return o||(o=await import("node-screenshots")),o}let u=null;async function v(){return u||(u=await import("sharp")),u}const w={sd:{scale:.5,jpegQuality:55},hd:{scale:1,jpegQuality:72},original:{scale:1,jpegQuality:95}};class M{constructor(){this.screens=[],this.interval=null,this.fps=20,this.quality="hd",this.activeScreenId=null,this.onFrame=null,this.capturing=!1}async listScreens(){try{const t=await S(),{Monitor:i}=t,a=i.all();return this.screens=a.map((e,s)=>({id:String(e.id()),name:e.name()||`Screen ${s+1}`,x:e.x(),y:e.y(),width:e.width(),height:e.height(),scaleFactor:e.scaleFactor()})),this.screens}catch(t){return console.error("[remote-desktop] listScreens failed:",t),this.screens=[{id:"0",name:"Screen 1",x:0,y:0,width:1920,height:1080,scaleFactor:1}],this.screens}}async captureVirtualDesktop(){const t=await S(),{Monitor:i}=t,a=i.all();if(a.length===0)throw new Error("No screens available");let e=a[0];if(this.activeScreenId){const c=a.find(n=>String(n.id())===this.activeScreenId);c&&(e=c)}const s=e.captureImageSync(),r=s.width,l=s.height;let h,p=r,g=l;try{const c=await v(),n=w[this.quality],f=Math.max(1,Math.round(r*n.scale)),y=Math.max(1,Math.round(l*n.scale)),m=s.toRawSync();h=await c(m,{raw:{width:r,height:l,channels:4}}).resize(f,y,{fit:"fill"}).jpeg({quality:n.jpegQuality}).toBuffer(),p=f,g=y}catch{h=s.toJpegSync()}return{data:h,width:p,height:g}}setActiveScreen(t){this.activeScreenId=t}startLoop(t){this.stopLoop(),this.onFrame=t,this.scheduleLoop()}scheduleLoop(){if(!this.onFrame)return;const t=Math.max(16,Math.round(1e3/this.fps));this.interval=setInterval(async()=>{if(!this.capturing){this.capturing=!0;try{const i=await this.captureVirtualDesktop();this.onFrame?.(i.data)}catch{}finally{this.capturing=!1}}},t)}stopLoop(){this.interval&&(clearInterval(this.interval),this.interval=null)}setQuality(t,i){t&&Object.prototype.hasOwnProperty.call(w,t)&&(this.quality=t),i!==void 0&&(this.fps=Math.max(1,Math.min(60,i))),this.interval&&(clearInterval(this.interval),this.interval=null,this.scheduleLoop())}getQuality(){return this.quality}getScreens(){return this.screens}}export{M as ScreenCapture};
|
|
1
|
+
let L=null;async function x(){return L||(L=await import("node-screenshots")),L}let T=null,I="pending";async function k(){if(!T)try{const u=await import("sharp");T=u&&u.default||u,I="ok",console.log("[remote-desktop] sharp \u52A0\u8F7D\u6210\u529F\uFF0C\u542F\u7528 tile \u589E\u91CF\u7F16\u7801 + JPEG \u538B\u7F29")}catch(u){return I="failed",console.warn("[remote-desktop] sharp \u52A0\u8F7D\u5931\u8D25\uFF0C\u964D\u7EA7 toJpegSync \u6574\u5E27\u7F16\u7801\uFF08\u65E0 tile \u589E\u91CF\uFF0C\u4E8B\u4EF6\u5FAA\u73AF\u963B\u585E\uFF0C\u5E27\u7387\u964D\u4F4E\uFF09:",u instanceof Error?u.message:u),T=null,null}return T}function j(){return I}const A={sd:{scale:.5,jpegQuality:55},hd:{scale:1,jpegQuality:72},original:{scale:1,jpegQuality:95}},_=1600,w=160,C=4,O=24,N=1,P=.5,U=4;function b(u,t,e,a,s,n){if(!u||t<=0||s<=0||n<=0)return null;const i=Math.max(0,e),l=Math.max(0,a),m=Math.min(t,e+s),c=Math.min(Math.floor(u.length/4/t),a+n),o=m-i,p=c-l;if(o<=0||p<=0)return null;const f=t*4,r=Buffer.allocUnsafe(o*p*4);for(let h=0;h<p;h++){const d=(l+h)*f+i*4;u.copy(r,h*o*4,d,d+o*4)}return r}function v(u,t,e,a,s){if(!t||t.length!==u.length||e<=0||a<=0)return Number.MAX_SAFE_INTEGER;const n=Math.max(0,s.left),i=Math.max(0,s.top),l=Math.min(e,s.left+s.w),m=Math.min(a,s.top+s.h),c=e*4;let o=0;for(let p=i;p<m;p+=C){const f=p*c;for(let r=n;r<l;r+=C){const h=f+r*4;Math.abs(u[h]-t[h])+Math.abs(u[h+1]-t[h+1])+Math.abs(u[h+2]-t[h+2])>O&&o++}}return o}class M{constructor(){this.screens=[],this.captureTimer=null,this.fps=20,this.quality="hd",this.activeScreenId=null,this.onFrame=null,this.captureInFlight=!1,this.encodePending=null,this.encoding=!1,this.consecutiveStillFrames=0,this.inputBoostFrames=0,this.lastRaw=null,this.lastRawWidth=0,this.lastRawHeight=0,this.pendingReference=null,this.frameStats={full:0,partial:0,null:0,sharpFail:0},this.cachedMonitors=null,this.lastFullFrameAt=0,this.consecutiveNullCaptures=0,this.backpressureThrottleUntil=0}resetFrameStats(){this.frameStats={full:0,partial:0,null:0,sharpFail:0}}async listScreens(){try{const t=await x(),{Monitor:e}=t,a=e.all();return this.cachedMonitors=a,this.screens=a.map((s,n)=>({id:String(s.id()),name:s.name()||`Screen ${n+1}`,x:s.x(),y:s.y(),width:s.width(),height:s.height(),scaleFactor:s.scaleFactor()})),this.screens}catch(t){return console.error("[remote-desktop] listScreens failed:",t),this.screens=[{id:"0",name:"Screen 1",x:0,y:0,width:1920,height:1080,scaleFactor:1}],this.screens}}async getActiveMonitor(){const t=await x();let e=this.cachedMonitors;if((!e||e.length===0)&&(e=t.Monitor.all(),this.cachedMonitors=e),e.length===0)throw new Error("No screens available");if(this.activeScreenId){const a=e.find(s=>String(s.id())===this.activeScreenId);if(a)return a}return e[0]}async acquireShot(){const t=await this.getActiveMonitor();let e;typeof t.captureImage=="function"?e=await t.captureImage():e=t.captureImageSync();let a;return typeof e.toRaw=="function"?a=await e.toRaw():a=e.toRawSync(),{image:e,raw:a,srcWidth:e.width,srcHeight:e.height}}async encodeShot(t,e){const{image:a,raw:s,srcWidth:n,srcHeight:i}=t;let l=null;try{l=await k()}catch{l=null}const m=A[this.quality];let c=Math.max(1,Math.round(n*m.scale)),o=Math.max(1,Math.round(i*m.scale));const p=Math.max(c,o);if(p>_){const r=_/p;c=Math.max(1,Math.round(c*r)),o=Math.max(1,Math.round(o*r))}const f=!!this.lastRaw&&this.lastRawWidth===n&&this.lastRawHeight===i;if(!l){if(this.frameStats.sharpFail++,!e&&f&&v(s,this.lastRaw,n,i,{left:0,top:0,w:n,h:i})<N)return this.frameStats.null++,null;let r;try{r=a.toJpegSync()}catch{return null}return this.pendingReference={raw:s,width:n,height:i},this.frameStats.full++,{kind:"full",data:r,width:n,height:i}}try{if(!e&&f){const d=this.detectChangedTiles(s,n,i,c,o);if(d.changedTileCount>0){const F=this.totalTileCount(c,o);if(d.changedTileCount<Math.ceil(F*P)){const y=await this.mapLimit(d.regions,U,async g=>{const E=b(s,n,g.sx,g.sy,g.sw,g.sh);if(!E)return null;try{const D=await l(E,{raw:{width:g.sw,height:g.sh,channels:4}}).resize(g.w,g.h,{fit:"fill"}).jpeg({quality:m.jpegQuality}).toBuffer();return{x:g.x,y:g.y,w:g.w,h:g.h,data:D}}catch{return null}}),S=y.filter(g=>g!==null),R=y.length!==S.length;return S.length===0?(this.frameStats.null++,null):(R||(this.pendingReference={raw:s,width:n,height:i}),this.frameStats.partial++,{kind:"partial",width:c,height:o,tiles:S})}}else return this.frameStats.null++,null}let r,h=!1;try{r=await l(s,{raw:{width:n,height:i,channels:4}}).resize(c,o,{fit:"fill"}).jpeg({quality:m.jpegQuality}).toBuffer()}catch(d){const F=s.slice(0,8).toString("hex");console.warn("[remote-desktop] sharp \u6574\u5E27\u7F16\u7801\u5931\u8D25\uFF0Cfallback toJpegSync:",`raw.length=${s.length} expected=${n*i*4} head=${F}`,d);try{r=a.toJpegSync(),h=!0}catch{return null}}return this.pendingReference={raw:s,width:n,height:i},h?((c!==n||o!==i)&&(this.pendingReference={raw:null,width:0,height:0}),this.frameStats.full++,{kind:"full",data:r,width:n,height:i}):(this.frameStats.full++,{kind:"full",data:r,width:c,height:o})}catch{return this.frameStats.null++,null}}async captureVirtualDesktop(t=!1){const e=await this.acquireShot(),a=await this.encodeShot(e,t);return this.discardReferenceFrame(),a}totalTileCount(t,e){const a=Math.max(1,Math.ceil(t/w)),s=Math.max(1,Math.ceil(e/w));return a*s}async mapLimit(t,e,a){const s=new Array(t.length);let n=0;const i=Math.max(1,Math.min(e,t.length));return await Promise.all(Array.from({length:i},async()=>{for(;;){const l=n++;if(l>=t.length)return;s[l]=await a(t[l])}})),s}outputRectToSource(t,e,a,s,n,i,l){const m=Math.max(0,Math.min(i-1,Math.round(t/n))),c=Math.max(0,Math.min(l-1,Math.round(e/n))),o=Math.max(1,Math.min(i-m,Math.round(a/n))),p=Math.max(1,Math.min(l-c,Math.round(s/n)));return{left:m,top:c,w:o,h:p}}detectChangedTiles(t,e,a,s,n){const i=s/e,l=Math.max(1,Math.ceil(s/w)),m=Math.max(1,Math.ceil(n/w)),c=new Array(m*l).fill(!1);let o=0;for(let f=0;f<m;f++)for(let r=0;r<l;r++){const h=r*w,d=f*w,F=Math.min(w,s-h),y=Math.min(w,n-d),S=this.outputRectToSource(h,d,F,y,i,e,a);v(t,this.lastRaw,e,a,S)>=N&&(c[f*l+r]=!0,o++)}const p=[];for(let f=0;f<m;f++){let r=0;for(;r<l;){if(!c[f*l+r]){r++;continue}let h=r;for(;h+1<l&&c[f*l+h+1];)h++;const d=r*w,F=f*w,y=Math.min(w*(h-r+1),s-d),S=Math.min(w,n-F),R=this.outputRectToSource(d,F,y,S,i,e,a);p.push({x:d,y:F,w:y,h:S,sx:R.left,sy:R.top,sw:R.w,sh:R.h}),r=h+1}}return{changedTileCount:o,regions:p}}commitReferenceFrame(){this.pendingReference&&(this.pendingReference.raw===null?(this.lastRaw=null,this.lastRawWidth=0,this.lastRawHeight=0):(this.lastRaw=this.pendingReference.raw,this.lastRawWidth=this.pendingReference.width,this.lastRawHeight=this.pendingReference.height),this.pendingReference=null)}discardReferenceFrame(){this.pendingReference=null}setActiveScreen(t){this.activeScreenId=t}startLoop(t){this.stopLoop(),this.onFrame=t,this.scheduleLoop()}scheduleLoop(t){if(!this.onFrame)return;this.captureTimer&&(clearTimeout(this.captureTimer),this.captureTimer=null);const e=t??this.currentIntervalMs();this.captureTimer=setTimeout(()=>{this.captureTimer=null,this.captureOnce()},Math.max(0,e))}async captureOnce(){if(this.captureInFlight){this.scheduleLoop();return}this.inputBoostFrames>0&&this.inputBoostFrames--,this.captureInFlight=!0;const t=Date.now();try{const e=await this.acquireShot(),a=Date.now()-t;this.scheduleLoop(Math.max(0,this.currentIntervalMs()-a)),this.encodePending=e,this.drainEncode()}catch{this.scheduleLoop()}finally{this.captureInFlight=!1}}async drainEncode(){if(!this.encoding){this.encoding=!0;try{for(;this.encodePending;){const t=this.encodePending;this.encodePending=null;const e=this.consecutiveStillFrames>=M.STILL_FRAME_COUNT,a=Date.now()-this.lastFullFrameAt>=M.PERIODIC_FULL_FRAME_INTERVAL_MS;let s=null;try{s=await this.encodeShot(t,e||a)}catch{}if(s){this.consecutiveStillFrames=0,this.consecutiveNullCaptures=0;const n=this.onFrame;(n?n(s):!1)!==!1?(s.kind==="full"&&(this.lastFullFrameAt=Date.now()),this.commitReferenceFrame()):this.discardReferenceFrame()}else if(this.consecutiveStillFrames++,this.consecutiveNullCaptures++,this.consecutiveNullCaptures>=8&&Date.now()-this.lastFullFrameAt>=3e3){const n=await this.captureSafeFullFrame();if(n){this.consecutiveStillFrames=0,this.consecutiveNullCaptures=0;const i=this.onFrame,l=i?i(n):!1;l!==!1?(this.lastFullFrameAt=Date.now(),this.commitReferenceFrame()):this.discardReferenceFrame(),console.warn(`[remote-desktop] \u5B89\u5168\u7F51\u89E6\u53D1\uFF1A\u8FDE\u7EED null \u5DF2\u8FBE\u9608\u503C\uFF0C\u5DF2\u7528 toJpegSync \u5F3A\u5236\u53D1 full \u5E27\uFF08sent=${l!==!1}\uFF09`)}}}}finally{this.encoding=!1}}}async captureSafeFullFrame(){try{const t=await this.getActiveMonitor();let e;typeof t.captureImage=="function"?e=await t.captureImage():e=t.captureImageSync();const a=e.toJpegSync();return this.pendingReference={raw:null,width:0,height:0},{kind:"full",data:a,width:e.width,height:e.height}}catch(t){return console.warn("[remote-desktop] \u5B89\u5168\u7F51 toJpegSync \u4E5F\u5931\u8D25:",t),null}}currentIntervalMs(){if(I==="failed")return this.inputBoostFrames>0?250:this.consecutiveStillFrames>=M.STILL_FRAME_COUNT?M.IDLE_INTERVAL_MS:500;if(this.inputBoostFrames>0)return M.ACTIVE_INTERVAL_MS;if(this.consecutiveStillFrames>=M.STILL_FRAME_COUNT)return M.IDLE_INTERVAL_MS;const t=Math.max(16,Math.round(1e3/this.fps));return Date.now()<this.backpressureThrottleUntil?Math.max(t,100):t}requestFrame(){this.inputBoostFrames=Math.max(this.inputBoostFrames,3),this.captureInFlight||this.scheduleLoop()}notifyFrameDropped(){this.backpressureThrottleUntil=Date.now()+1e3}requestFullFrame(){this.lastRaw=null,this.lastRawWidth=0,this.lastRawHeight=0,this.scheduleLoop()}stopLoop(){this.captureTimer&&(clearTimeout(this.captureTimer),this.captureTimer=null),this.onFrame=null,this.consecutiveStillFrames=0,this.inputBoostFrames=0,this.encodePending=null,this.captureInFlight=!1,this.encoding=!1}setQuality(t,e){t&&Object.prototype.hasOwnProperty.call(A,t)&&(this.quality=t,this.lastRaw=null,this.lastRawWidth=0,this.lastRawHeight=0),e!==void 0&&(this.fps=Math.max(1,Math.min(60,e))),this.onFrame&&this.scheduleLoop()}getQuality(){return this.quality}getScreens(){return this.screens}}M.ACTIVE_INTERVAL_MS=33,M.STILL_FRAME_COUNT=3,M.IDLE_INTERVAL_MS=1e3,M.PERIODIC_FULL_FRAME_INTERVAL_MS=2e3;export{M as ScreenCapture,b as extractRawRegion,j as getSharpLoadStatus};
|
|
2
2
|
|
|
@@ -3,7 +3,7 @@ import { ScreenCapture } from "./screen-capture.js";
|
|
|
3
3
|
import { InputDispatcher } from "./input-dispatcher.js";
|
|
4
4
|
import { PermissionManager } from "./permission-manager.js";
|
|
5
5
|
import { ComputerControl } from "./computer-control.js";
|
|
6
|
-
import type { InputMessage, RemoteDesktopCapability, ScreenInfo, StartResult } from "./types.js";
|
|
6
|
+
import type { InputMessage, RemoteDesktopCapability, ScreenInfo, StartResult, CaptureFrame } from "./types.js";
|
|
7
7
|
export declare class RemoteDesktopSession {
|
|
8
8
|
|
|
9
9
|
static readonly FOCUS_POLL_INTERVAL_MS = 1000;
|
|
@@ -16,6 +16,7 @@ export declare class RemoteDesktopSession {
|
|
|
16
16
|
state: "active" | "closed" | "error";
|
|
17
17
|
ws: WebSocket | null;
|
|
18
18
|
private inputBackend;
|
|
19
|
+
|
|
19
20
|
private onFrame;
|
|
20
21
|
private focusPollTimer;
|
|
21
22
|
private lastFocusState;
|
|
@@ -40,7 +41,7 @@ export declare class RemoteDesktopSession {
|
|
|
40
41
|
|
|
41
42
|
private sendFocusState;
|
|
42
43
|
attachWebSocket(ws: WebSocket): void;
|
|
43
|
-
startFrameLoop(onFrame: (frame:
|
|
44
|
+
startFrameLoop(onFrame: (frame: CaptureFrame) => boolean | void): void;
|
|
44
45
|
stopFrameLoop(): void;
|
|
45
46
|
|
|
46
47
|
handleInput(message: InputMessage): Promise<void>;
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{randomUUID as a}from"node:crypto";import{ScreenCapture as c}from"./screen-capture.js";import{InputDispatcher as h}from"./input-dispatcher.js";import{ComputerControl as u}from"./computer-control.js";import{SystemBackend as p}from"./system-backend.js";class o{constructor(s,t,e){this.inputDispatcher=null,this.state="active",this.ws=null,this.onFrame=null,this.focusPollTimer=null,this.lastFocusState=null,this.inputChain=Promise.resolve(),this.id=s,this.agentId=t,this.permissions=e,this.screenCapture=new c,this.inputBackend=new p,this.computerControl=new u(this.screenCapture,this.inputBackend)}async init(){const s=await this.screenCapture.listScreens(),t=s[0];return t&&(this.inputDispatcher=new h(this.inputBackend,this.permissions,t),this.inputBackend.setScreenSize(t.width,t.height),this.inputBackend.setActiveScreen(t)),{screens:s}}async switchScreen(s){this.screenCapture.setActiveScreen(s);const t=this.screenCapture.getScreens(),e=s?t.find(i=>i.id===s):t[0];e&&this.inputDispatcher&&(this.inputDispatcher.setScreen(e),this.inputBackend.setScreenSize(e.width,e.height),this.inputBackend.setActiveScreen(e))}startInputBackend(){this.inputBackend.startPersistentProcess()}async enableControl(){this.inputBackend.startPersistentProcess(),this.startFocusPolling(),await this.inputBackend.centerMouse()}stopInputBackend(){this.stopFocusPolling(),this.inputBackend.stopPersistentProcess()}startFocusPolling(){this.stopFocusPolling(),this.focusPollTimer=setInterval(()=>{const s=this.inputBackend.isInputFocused();s!==this.lastFocusState&&(this.lastFocusState=s,this.sendFocusState(s))},o.FOCUS_POLL_INTERVAL_MS)}stopFocusPolling(){this.focusPollTimer&&(clearInterval(this.focusPollTimer),this.focusPollTimer=null),this.lastFocusState=null}sendFocusState(s){!this.ws||this.ws.readyState!==this.ws.OPEN||this.ws.send(JSON.stringify({type:"remote-desktop:focus-state",sessionId:this.id,data:{hasInputFocus:s}}))}attachWebSocket(s){this.ws=s}startFrameLoop(s){this.onFrame=s,this.screenCapture.startLoop(s)}stopFrameLoop(){this.screenCapture.stopLoop(),this.onFrame=null}handleInput(s){if(!this.inputDispatcher)return Promise.resolve();const t=this.inputChain.then(()=>this.inputDispatcher.handle(s));return this.inputChain=t.catch(()=>{}),t}close(){this.state="closed",this.stopFocusPolling(),this.stopFrameLoop(),this.inputBackend.stopPersistentProcess(),this.ws=null}}o.FOCUS_POLL_INTERVAL_MS=1e3;class P{constructor(s){this.sessions=new Map,this.agentSessions=new Map,this.permissionManager=s}async createSession(s,t){if(!this.permissionManager.validateToken(t))throw new Error("Invalid token");const e=this.agentSessions.get(s);e&&this.closeSession(e);const i=this.permissionManager.resolvePermissions(t),n=a(),r=new o(n,s,i);return await r.init(),this.sessions.set(n,r),this.agentSessions.set(s,n),{sessionId:n,screens:r.screenCapture.getScreens(),permissions:Array.from(i)}}getSession(s){return this.sessions.get(s)}async handleInput(s,t){const e=this.sessions.get(s);!e||e.state!=="active"||await e.handleInput(t)}closeSession(s){const t=this.sessions.get(s);if(t){t.close(),this.sessions.delete(s);for(const[e,i]of this.agentSessions)if(i===s){this.agentSessions.delete(e);break}}}closeAll(){for(const s of this.sessions.values())s.close();this.sessions.clear(),this.agentSessions.clear()}}export{o as RemoteDesktopSession,P as SessionManager};
|
|
1
|
+
import{randomUUID as a}from"node:crypto";import{ScreenCapture as c}from"./screen-capture.js";import{InputDispatcher as h}from"./input-dispatcher.js";import{ComputerControl as u}from"./computer-control.js";import{SystemBackend as p}from"./system-backend.js";class o{constructor(s,t,e){this.inputDispatcher=null,this.state="active",this.ws=null,this.onFrame=null,this.focusPollTimer=null,this.lastFocusState=null,this.inputChain=Promise.resolve(),this.id=s,this.agentId=t,this.permissions=e,this.screenCapture=new c,this.inputBackend=new p,this.computerControl=new u(this.screenCapture,this.inputBackend)}async init(){const s=await this.screenCapture.listScreens(),t=s[0];return t&&(this.inputDispatcher=new h(this.inputBackend,this.permissions,t),this.inputBackend.setScreenSize(t.width,t.height),this.inputBackend.setActiveScreen(t)),{screens:s}}async switchScreen(s){this.screenCapture.setActiveScreen(s);const t=this.screenCapture.getScreens(),e=s?t.find(i=>i.id===s):t[0];e&&this.inputDispatcher&&(this.inputDispatcher.setScreen(e),this.inputBackend.setScreenSize(e.width,e.height),this.inputBackend.setActiveScreen(e))}startInputBackend(){this.inputBackend.startPersistentProcess()}async enableControl(){this.inputBackend.startPersistentProcess(),this.startFocusPolling(),await this.inputBackend.centerMouse()}stopInputBackend(){this.stopFocusPolling(),this.inputBackend.stopPersistentProcess()}startFocusPolling(){this.stopFocusPolling(),this.focusPollTimer=setInterval(()=>{const s=this.inputBackend.isInputFocused();s!==this.lastFocusState&&(this.lastFocusState=s,this.sendFocusState(s))},o.FOCUS_POLL_INTERVAL_MS)}stopFocusPolling(){this.focusPollTimer&&(clearInterval(this.focusPollTimer),this.focusPollTimer=null),this.lastFocusState=null}sendFocusState(s){!this.ws||this.ws.readyState!==this.ws.OPEN||this.ws.send(JSON.stringify({type:"remote-desktop:focus-state",sessionId:this.id,data:{hasInputFocus:s}}))}attachWebSocket(s){this.ws=s}startFrameLoop(s){this.onFrame=s,this.screenCapture.startLoop(s)}stopFrameLoop(){this.screenCapture.stopLoop(),this.onFrame=null}handleInput(s){if(!this.inputDispatcher)return Promise.resolve();this.screenCapture.requestFrame();const t=this.inputChain.then(async()=>{await this.inputDispatcher.handle(s),this.screenCapture.requestFrame()});return this.inputChain=t.catch(()=>{}),t}close(){this.state="closed",this.stopFocusPolling(),this.stopFrameLoop(),this.inputBackend.stopPersistentProcess(),this.ws=null}}o.FOCUS_POLL_INTERVAL_MS=1e3;class P{constructor(s){this.sessions=new Map,this.agentSessions=new Map,this.permissionManager=s}async createSession(s,t){if(!this.permissionManager.validateToken(t))throw new Error("Invalid token");const e=this.agentSessions.get(s);e&&this.closeSession(e);const i=this.permissionManager.resolvePermissions(t),n=a(),r=new o(n,s,i);return await r.init(),this.sessions.set(n,r),this.agentSessions.set(s,n),{sessionId:n,screens:r.screenCapture.getScreens(),permissions:Array.from(i)}}getSession(s){return this.sessions.get(s)}async handleInput(s,t){const e=this.sessions.get(s);!e||e.state!=="active"||await e.handleInput(t)}closeSession(s){const t=this.sessions.get(s);if(t){t.close(),this.sessions.delete(s);for(const[e,i]of this.agentSessions)if(i===s){this.agentSessions.delete(e);break}}}closeAll(){for(const s of this.sessions.values())s.close();this.sessions.clear(),this.agentSessions.clear()}}export{o as RemoteDesktopSession,P as SessionManager};
|
|
2
2
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import{spawn as y,exec as P}from"node:child_process";import{promisify as E}from"node:util";import{mapKey as a}from"./input-backends/key-map.js";import{toOsMouseCoords as p}from"./coords.js";import{initWin32Api as S,MOUSEEVENTF_LEFTDOWN as h,MOUSEEVENTF_LEFTUP as u,MOUSEEVENTF_RIGHTDOWN as m,MOUSEEVENTF_RIGHTUP as d,MOUSEEVENTF_WHEEL as k,MOUSEEVENTF_HWHEEL as v,KEYEVENTF_KEYUP as f}from"./win32-api.js";const r=E(P);function g(){return process.platform==="win32"?"windows":process.platform==="darwin"?"macos":"linux"}class l{constructor(s){this.screenSize={width:1920,height:1080},this.activeScreen=null,this.win32Api=null,this.psProcess=null,this.psReady=!1,this.psReadyPromise=null,this.psBuffer="",this.cliclickProcess=null,this.xdotoolProcess=null,this.platform=s??g()}startPersistentProcess(){if(this.platform==="windows"){if(this.win32Api=S(),this.win32Api){console.log("[remote-desktop] Win32 FFI API ready (koffi)");return}console.warn("[remote-desktop] Win32 FFI unavailable, falling back to PowerShell"),this.startPowerShellProcess()}}startPowerShellProcess(){this.psProcess||this.psReadyPromise||(this.psReadyPromise=new Promise(s=>{const e=y("powershell.exe",["-NoProfile","-NoLogo","-Command","-"],{stdio:["pipe","pipe","pipe"],windowsHide:!0});e.stdin?.write(`${l.WIN32_INIT_TYPES}; [W]::SetProcessDPIAware() | Out-Null; Add-Type -AssemblyName System.Windows.Forms; echo "__READY__"
|
|
2
2
|
`),e.stdout?.on("data",t=>{this.psBuffer+=t.toString(),this.psBuffer.includes("__READY__")&&(this.psBuffer="",this.psProcess=e,this.psReady=!0,console.log("[remote-desktop] PowerShell persistent process ready"),s(!0))}),e.stderr?.on("data",t=>{console.warn("[remote-desktop] PowerShell stderr:",t.toString())}),e.on("error",t=>{console.error("[remote-desktop] PowerShell process error:",t),this.psProcess=null,this.psReady=!1,this.psReadyPromise=null,s(!1)}),e.on("exit",()=>{this.psProcess=null,this.psReady=!1,this.psReadyPromise=null,s(!1)})}))}async ensurePowerShell(){return this.psProcess?.stdin?.writable&&this.psReady?!0:(this.psReadyPromise||this.startPowerShellProcess(),await this.psReadyPromise??!1)}stopPersistentProcess(){this.psProcess&&(this.psProcess.stdin?.end(),this.psProcess.kill(),this.psProcess=null,this.psReady=!1,this.psReadyPromise=null),this.cliclickProcess&&(this.cliclickProcess.kill(),this.cliclickProcess=null),this.xdotoolProcess&&(this.xdotoolProcess.kill(),this.xdotoolProcess=null)}psCmd(s){if(this.psProcess?.stdin?.writable&&this.psReady){this.psProcess.stdin.write(s+`
|
|
3
3
|
`);return}const e=`${l.WIN32_INIT_TYPES}; [W]::SetProcessDPIAware() | Out-Null; Add-Type -AssemblyName System.Windows.Forms; ${s}`,t=Buffer.from(e,"utf16le").toString("base64");r(`powershell.exe -NoProfile -EncodedCommand ${t}`).catch(i=>{console.warn("[remote-desktop] fallback PowerShell command failed:",i.message)})}async moveMouse(s,e){const{x:t,y:i}=p(this.activeScreen,s,e,this.platform);if(this.platform==="windows"){if(this.win32Api){this.win32Api.setCursorPos(t,i);return}this.psCmd(`[W]::SetCursorPos(${t}, ${i})`)}else this.platform==="macos"?r(`cliclick m:${t},${i}`).catch(o=>this.warnInputErr("macOS moveMouse",o)):r(`xdotool mousemove ${t} ${i}`).catch(o=>this.warnInputErr("linux moveMouse",o))}async moveMouseRelative(s,e){if(this.platform==="windows"){if(this.win32Api){const o=this.win32Api.getCursorPos();o?this.win32Api.setCursorPos(o.x+Math.round(s),o.y+Math.round(e)):this.warnInputErr("win32 moveMouseRelative",new Error("getCursorPos returned null"));return}this.psCmd(`[W]::mouse_event(1, ${Math.round(s)}, ${Math.round(e)}, 0, 0)`);return}const{x:t,y:i}=await this.getMousePos();await this.moveMouse(t+s,i+e)}async centerMouse(){this.activeScreen&&await this.moveMouse(this.activeScreen.x+this.activeScreen.width/2,this.activeScreen.y+this.activeScreen.height/2)}async click(s,e,t){const{x:i,y:o}=p(this.activeScreen,s,e,this.platform);if(this.platform==="windows"){if(this.win32Api){const w=t==="right"?m:h,x=t==="right"?d:u;this.win32Api.setCursorPos(i,o),this.win32Api.mouseEvent(w,0,0,0),this.win32Api.mouseEvent(x,0,0,0);return}const n=t==="right"?8:2,c=t==="right"?16:4;this.psCmd(`[W]::SetCursorPos(${i},${o}); [W]::mouse_event(${n},0,0,0,0); [W]::mouse_event(${c},0,0,0,0)`)}else this.platform==="macos"?r(`cliclick ${t==="right"?"rc":"c"}:${i},${o}`).catch(c=>this.warnInputErr("macOS click",c)):r(`xdotool mousemove ${i} ${o} && xdotool click ${t==="right"?3:1}`).catch(c=>this.warnInputErr("linux click",c))}async doubleClick(s,e){const{x:t,y:i}=p(this.activeScreen,s,e,this.platform);if(this.platform==="windows"){if(this.win32Api){this.win32Api.setCursorPos(t,i);for(let o=0;o<2;o++)this.win32Api.mouseEvent(h,0,0,0),this.win32Api.mouseEvent(u,0,0,0);return}this.psCmd(`[W]::SetCursorPos(${t},${i}); [W]::mouse_event(2,0,0,0,0); [W]::mouse_event(4,0,0,0,0); [W]::mouse_event(2,0,0,0,0); [W]::mouse_event(4,0,0,0,0)`)}else this.platform==="macos"?r(`cliclick dd:${t},${i}`).catch(o=>this.warnInputErr("macOS doubleClick",o)):r(`xdotool mousemove ${t} ${i} && xdotool click --repeat 2 1`).catch(o=>this.warnInputErr("linux doubleClick",o))}async mouseDown(s){if(this.platform==="windows"){if(this.win32Api){const t=s==="right"?m:h;this.win32Api.mouseEvent(t,0,0,0);return}const e=s==="right"?8:2;this.psCmd(`[W]::mouse_event(${e},0,0,0,0)`)}else this.platform==="macos"?r(`cliclick ${s==="right"?"rd":"dd"}:`).catch(t=>this.warnInputErr("macOS mouseDown",t)):r(`xdotool mousedown ${s==="right"?3:1}`).catch(t=>this.warnInputErr("linux mouseDown",t))}async mouseUp(s){if(this.platform==="windows"){if(this.win32Api){const t=s==="right"?d:u;this.win32Api.mouseEvent(t,0,0,0);return}const e=s==="right"?16:4;this.psCmd(`[W]::mouse_event(${e},0,0,0,0)`)}else this.platform==="macos"?r(`cliclick ${s==="right"?"ru":"du"}:`).catch(t=>this.warnInputErr("macOS mouseUp",t)):r(`xdotool mouseup ${s==="right"?3:1}`).catch(t=>this.warnInputErr("linux mouseUp",t))}async scroll(s,e){const t=Math.max(1,Math.round(e));if(this.platform==="windows"){const i=s==="left"||s==="right",o=i?v:k,n=i?s==="right"?t*120:-t*120:s==="down"?-t*120:t*120;if(this.win32Api){this.win32Api.mouseEvent(o,0,0,n);return}this.psCmd(`[W]::mouse_event(${o},0,0,${n},0)`)}else this.platform==="macos"?r(`python3 -c "from Quartz import CGEventCreateScrollWheelEvent2, kCGScrollEventUnitPixel; from CoreGraphics import CGEventPost, kCGHIDEventTap; e = CGEventCreateScrollWheelEvent2(None, kCGScrollEventUnitPixel, ${s==="left"||s==="right"?1:0}, ${(s==="down"||s==="right"?-1:1)*t*10}); CGEventPost(kCGHIDEventTap, e)" 2>/dev/null || true`).catch(n=>this.warnInputErr("macOS scroll",n)):r(`xdotool click --repeat ${t} ${s==="up"?4:s==="down"?5:s==="left"?6:7}`).catch(o=>this.warnInputErr("linux scroll",o))}async type(s){if(this.platform==="windows"){const t=`[System.Windows.Forms.SendKeys]::SendWait('${s.replace(/[{}()[\]^%~+]/g,"{$&}").replace(/\n/g,"{ENTER}").replace(/'/g,"''")}')`;if(await this.ensurePowerShell()){this.psProcess?.stdin?.write(t+`
|
|
4
|
-
`);return}this.psCmd(t)}else this.platform==="macos"?r(`cliclick t:'${s.replace(/'/g,"'\\''")}'`).catch(e=>this.warnInputErr("macOS type",e)):r(`xdotool type --clearmodifiers '${s.replace(/'/g,"'\\''")}'`).catch(e=>this.warnInputErr("linux type",e))}async keyPress(s){const e=s.map(a);if(this.platform==="windows"){if(this.win32Api){for(const t of e){const i=this.getVirtualKeyCode(t);i&&(this.win32Api.keybdEvent(i,0),await new Promise(o=>setTimeout(o,10)),this.win32Api.keybdEvent(i,f))}return}for(const t of e){const i=this.getVirtualKeyCode(t);i&&this.psCmd(`[W]::keybd_event(${i},0,0,0); Start-Sleep -Milliseconds 10; [W]::keybd_event(${i},0,2,0)`)}}else this.platform==="macos"?r(`cliclick kp:${e.join("+")}`).catch(t=>this.warnInputErr("macOS keyPress",t)):r(`xdotool key '${e.join("+")}'`).catch(t=>this.warnInputErr("linux keyPress",t))}async keyDown(s){const e=a(s);if(this.platform==="windows"){const t=this.getVirtualKeyCode(e);if(!t)return;if(this.win32Api){this.win32Api.keybdEvent(t,0);return}this.psCmd(`[W]::keybd_event(${t},0,0,0)`)}else this.platform==="macos"?r(`cliclick kd:${e}`).catch(t=>this.warnInputErr("macOS keyDown",t)):r(`xdotool keydown ${e}`).catch(t=>this.warnInputErr("linux keyDown",t))}async keyUp(s){const e=a(s);if(this.platform==="windows"){const t=this.getVirtualKeyCode(e);if(!t)return;if(this.win32Api){this.win32Api.keybdEvent(t,f);return}this.psCmd(`[W]::keybd_event(${t},0,2,0)`)}else this.platform==="macos"?r(`cliclick ku:${e}`).catch(t=>this.warnInputErr("macOS keyUp",t)):r(`xdotool keyup ${e}`).catch(t=>this.warnInputErr("linux keyUp",t))}getScreenSize(){return this.screenSize}setScreenSize(s,e){this.screenSize={width:s,height:e}}setActiveScreen(s){this.activeScreen=s,this.screenSize={width:s.width,height:s.height}}async getMousePos(){if(this.platform==="windows"){if(this.win32Api)return this.win32Api.getCursorPos()??{x:0,y:0};const s=`${l.WIN32_INIT_TYPES}; [W]::SetProcessDPIAware() | Out-Null; Add-Type -AssemblyName System.Windows.Forms; $p = [System.Windows.Forms.Cursor]::Position; Write-Output "$($p.X),$($p.Y)"`,e=Buffer.from(s,"utf16le").toString("base64"),{stdout:t}=await r(`powershell.exe -NoProfile -EncodedCommand ${e}`),[i,o]=t.trim().split(",").map(Number);return{x:i||0,y:o||0}}else if(this.platform==="macos"){const{stdout:s}=await r(`osascript -e 'tell application "System Events" to get position of the mouse'`),[e,t]=s.trim().split(",").map(Number);return{x:e||0,y:t||0}}else{const{stdout:s}=await r("xdotool getmouselocation --shell"),e=s.match(/X=(\d+)/),t=s.match(/Y=(\d+)/);return{x:e?Number(e[1]):0,y:t?Number(t[1]):0}}}isInputFocused(){return this.platform!=="windows"||!this.win32Api?!1:this.win32Api.isInputFocused()}warnInputErr(s,e){console.warn(`[remote-desktop] ${s} failed:`,e?.message??String(e))}getVirtualKeyCode(s){return{a:65,b:66,c:67,d:68,e:69,f:70,g:71,h:72,i:73,j:74,k:75,l:76,m:77,n:78,o:79,p:80,q:81,r:82,s:83,t:84,u:85,v:86,w:87,x:88,y:89,z:90,0:48,1:49,2:50,3:51,4:52,5:53,6:54,7:55,8:56,9:57,enter:13,return:13,tab:9,escape:27,backspace:8,delete:46,up:38,down:40,left:37,right:39,home:36,end:35,pageup:33,pagedown:34,space:32,shift:16,control:17,alt:18,f1:112,f2:113,f3:114,f4:115,f5:116,f6:117,f7:118,f8:119,f9:120,f10:121,f11:122,f12:123}[s.toLowerCase()]||0}}l.WIN32_INIT_TYPES=`Add-Type -TypeDefinition 'using System; using System.Runtime.InteropServices; public class W { [DllImport("user32.dll")] public static extern bool SetProcessDPIAware(); [DllImport("user32.dll")] public static extern void mouse_event(uint f, int dx, int dy, int c, IntPtr i); [DllImport("user32.dll")] public static extern bool SetCursorPos(int x, int y); [DllImport("user32.dll")] public static extern void keybd_event(byte bVk, byte bScan, uint dwFlags, IntPtr dwExtraInfo); }'`;export{l as SystemBackend};
|
|
4
|
+
`);return}this.psCmd(t)}else this.platform==="macos"?r(`cliclick t:'${s.replace(/'/g,"'\\''")}'`).catch(e=>this.warnInputErr("macOS type",e)):r(`xdotool type --clearmodifiers '${s.replace(/'/g,"'\\''")}'`).catch(e=>this.warnInputErr("linux type",e))}async keyPress(s){const e=s.map(a);if(this.platform==="windows"){if(this.win32Api){for(const t of e){const i=this.getVirtualKeyCode(t);i&&(this.win32Api.keybdEvent(i,0),await new Promise(o=>setTimeout(o,10)),this.win32Api.keybdEvent(i,f))}return}for(const t of e){const i=this.getVirtualKeyCode(t);i&&this.psCmd(`[W]::keybd_event(${i},0,0,0); Start-Sleep -Milliseconds 10; [W]::keybd_event(${i},0,2,0)`)}}else this.platform==="macos"?r(`cliclick kp:${e.join("+")}`).catch(t=>this.warnInputErr("macOS keyPress",t)):r(`xdotool key '${e.join("+")}'`).catch(t=>this.warnInputErr("linux keyPress",t))}async keyDown(s){const e=a(s);if(this.platform==="windows"){const t=this.getVirtualKeyCode(e);if(!t)return;if(this.win32Api){this.win32Api.keybdEvent(t,0);return}this.psCmd(`[W]::keybd_event(${t},0,0,0)`)}else this.platform==="macos"?r(`cliclick kd:${e}`).catch(t=>this.warnInputErr("macOS keyDown",t)):r(`xdotool keydown ${e}`).catch(t=>this.warnInputErr("linux keyDown",t))}async keyUp(s){const e=a(s);if(this.platform==="windows"){const t=this.getVirtualKeyCode(e);if(!t)return;if(this.win32Api){this.win32Api.keybdEvent(t,f);return}this.psCmd(`[W]::keybd_event(${t},0,2,0)`)}else this.platform==="macos"?r(`cliclick ku:${e}`).catch(t=>this.warnInputErr("macOS keyUp",t)):r(`xdotool keyup ${e}`).catch(t=>this.warnInputErr("linux keyUp",t))}getScreenSize(){return this.screenSize}setScreenSize(s,e){this.screenSize={width:s,height:e}}setActiveScreen(s){this.activeScreen=s,this.screenSize={width:s.width,height:s.height}}async getMousePos(){if(this.platform==="windows"){if(this.win32Api)return this.win32Api.getCursorPos()??{x:0,y:0};const s=`${l.WIN32_INIT_TYPES}; [W]::SetProcessDPIAware() | Out-Null; Add-Type -AssemblyName System.Windows.Forms; $p = [System.Windows.Forms.Cursor]::Position; Write-Output "$($p.X),$($p.Y)"`,e=Buffer.from(s,"utf16le").toString("base64"),{stdout:t}=await r(`powershell.exe -NoProfile -EncodedCommand ${e}`),[i,o]=t.trim().split(",").map(Number);return{x:i||0,y:o||0}}else if(this.platform==="macos"){const{stdout:s}=await r(`osascript -e 'tell application "System Events" to get position of the mouse'`),[e,t]=s.trim().split(",").map(Number);return{x:e||0,y:t||0}}else{const{stdout:s}=await r("xdotool getmouselocation --shell"),e=s.match(/X=(\d+)/),t=s.match(/Y=(\d+)/);return{x:e?Number(e[1]):0,y:t?Number(t[1]):0}}}isInputFocused(){return this.platform!=="windows"||!this.win32Api?!1:this.win32Api.isInputFocused()}warnInputErr(s,e){console.warn(`[remote-desktop] ${s} failed:`,e?.message??String(e))}getVirtualKeyCode(s){return{a:65,b:66,c:67,d:68,e:69,f:70,g:71,h:72,i:73,j:74,k:75,l:76,m:77,n:78,o:79,p:80,q:81,r:82,s:83,t:84,u:85,v:86,w:87,x:88,y:89,z:90,0:48,1:49,2:50,3:51,4:52,5:53,6:54,7:55,8:56,9:57,enter:13,return:13,tab:9,escape:27,capslock:20,backspace:8,delete:46,up:38,down:40,left:37,right:39,home:36,end:35,pageup:33,pagedown:34,space:32,shift:16,control:17,alt:18,f1:112,f2:113,f3:114,f4:115,f5:116,f6:117,f7:118,f8:119,f9:120,f10:121,f11:122,f12:123}[s.toLowerCase()]||0}}l.WIN32_INIT_TYPES=`Add-Type -TypeDefinition 'using System; using System.Runtime.InteropServices; public class W { [DllImport("user32.dll")] public static extern bool SetProcessDPIAware(); [DllImport("user32.dll")] public static extern void mouse_event(uint f, int dx, int dy, int c, IntPtr i); [DllImport("user32.dll")] public static extern bool SetCursorPos(int x, int y); [DllImport("user32.dll")] public static extern void keybd_event(byte bVk, byte bScan, uint dwFlags, IntPtr dwExtraInfo); }'`;export{l as SystemBackend};
|
|
5
5
|
|
|
@@ -31,6 +31,31 @@ export interface ScreenshotResult {
|
|
|
31
31
|
height: number;
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
+
export interface TileRect {
|
|
35
|
+
|
|
36
|
+
x: number;
|
|
37
|
+
|
|
38
|
+
y: number;
|
|
39
|
+
|
|
40
|
+
w: number;
|
|
41
|
+
|
|
42
|
+
h: number;
|
|
43
|
+
|
|
44
|
+
data: Buffer;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export type CaptureFrame = {
|
|
48
|
+
kind: "full";
|
|
49
|
+
data: Buffer;
|
|
50
|
+
width: number;
|
|
51
|
+
height: number;
|
|
52
|
+
} | {
|
|
53
|
+
kind: "partial";
|
|
54
|
+
width: number;
|
|
55
|
+
height: number;
|
|
56
|
+
tiles: TileRect[];
|
|
57
|
+
};
|
|
58
|
+
|
|
34
59
|
export type RemoteDesktopQuality = "sd" | "hd" | "original";
|
|
35
60
|
export interface StartResult {
|
|
36
61
|
sessionId: string;
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{Router as
|
|
1
|
+
import{Router as f}from"express";import{createBrowserConnectToken as w}from"../browser/browser-connect-token.js";import{DEFAULT_BROWSER_ID as I}from"../browser/types.js";import{validateToken as i}from"../middleware/auth.js";import{closeBrowserSessionById as b,isBrowserSessionManagerStarted as d,listBrowserWsUrls as g}from"../services/browser-session-manager.js";import{createLogger as k}from"../utils/logger.js";const c=k("browser-sessions"),m=f();m.post("/browser-sessions/:agentId/connect-token",i,(o,s)=>{const r=String(o.params.agentId??"").trim();if(!r){s.status(400).json({ok:!1,error:"agentId is required"});return}const e=o.body??{},t=typeof e.browserId=="string"&&e.browserId.trim()?e.browserId.trim():I;try{if(d()&&!g(r).some(u=>u.browserId===t)){s.status(404).json({ok:!1,error:"browser session not found"});return}const n=w(r,t);if(!n){s.status(500).json({ok:!1,error:"failed to create token"});return}s.json({ok:!0,...n})}catch(n){const a=n;c.error("[browser-sessions] \u7B7E\u53D1\u8FDE\u63A5\u4EE4\u724C\u5931\u8D25: %s",a.message),s.status(500).json({ok:!1,error:a.message})}}),m.get("/browser-sessions/:agentId",i,async(o,s)=>{const r=String(o.params.agentId??"");if(!r.trim()){s.status(400).json({ok:!1,error:"agentId is required"});return}try{if(!d()){s.json({ok:!0,agentId:r,sessions:[]});return}const e=g(r);s.json({ok:!0,agentId:r,sessions:e})}catch(e){const t=e;c.error("[browser-sessions] \u5217\u8868\u5931\u8D25: %s",t.message),s.status(500).json({ok:!1,error:t.message})}}),m.delete("/browser-sessions/:agentId/:browserId",i,async(o,s)=>{const r=String(o.params.agentId??""),e=String(o.params.browserId??"");if(!r.trim()||!e.trim()){s.status(400).json({ok:!1,error:"agentId and browserId are required"});return}try{if(!d()){s.json({ok:!0,agentId:r,browserId:e});return}await b(r,e),s.json({ok:!0,agentId:r,browserId:e})}catch(t){const n=t;c.error("[browser-sessions] \u5173\u95ED\u5931\u8D25: %s",n.message),s.status(500).json({ok:!1,error:n.message})}});export{m as browserSessionsRouter};
|
|
2
2
|
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import k,{AxiosError as U}from"axios";import m from"ws";import{getRuntimeAccessToken as
|
|
1
|
+
import k,{AxiosError as U}from"axios";import m from"ws";import{getRuntimeAccessToken as E,normalizeSchedulerBaseUrl as h}from"./runtime-binding.js";import{loadConfigs as _,loadPrimaryLifecycleConfig as B}from"./auto-register.js";import{logger as c}from"../utils/logger.js";import{port as T}from"../config.js";const A=15e3,I=5e3,N=200,O=1*1024*1024,M=4001,w=new Map;function L(e){const n=e.serverUrl,r=e.instanceId;if(!n||!r)return null;try{const t=new URL(n),o=t.protocol==="https:"?"wss:":"ws:",i=E(n),l=_().find(a=>h(a.serverUrl)===n)||B(),s=i||l.userKey;if(i?c.debug("[TunnelClient] \u4F7F\u7528 runtime access token \u8FDE\u63A5\u96A7\u9053"):l.userKey&&c.warn("[TunnelClient] getRuntimeAccessToken \u8FD4\u56DE\u7A7A\uFF0C\u56DE\u9000\u5230 userKey\uFF08\u975E runtime token\uFF0C\u9274\u6743\u53EF\u80FD\u5931\u8D25\uFF09: targetUrl=",n),!s)return c.warn("[TunnelClient] \u7F3A\u5C11 runtime token\uFF0C\u65E0\u6CD5\u5EFA\u7ACB\u96A7\u9053: targetUrl=",n),null;const u=new URLSearchParams({instanceId:r});return{url:`${o}//${t.host}/ws/bridge-tunnel?${u.toString()}`,token:s}}catch(t){return c.warn("[TunnelClient] \u65E0\u6CD5\u89E3\u6790 serverUrl:",String(t)),null}}function j(e,n){const r=h(e);if(!r){c.warn("[TunnelClient] serverUrl \u65E0\u6548\uFF0C\u65E0\u6CD5\u542F\u52A8\u96A7\u9053:",e);return}const t=w.get(r);t&&(t.shuttingDown=!0,S(t),w.delete(r));const o={serverUrl:r,instanceId:n,streams:new Map,shuttingDown:!1};w.set(r,o),y(o)}function z(){for(const e of w.values())e.shuttingDown=!0,S(e);w.clear()}function S(e){e.timer&&(clearInterval(e.timer),e.timer=void 0),e.reconnectTimer&&(clearTimeout(e.reconnectTimer),e.reconnectTimer=void 0);for(const[n,r]of e.streams.entries()){try{r.ws.close(1e3,"tunnel client stopped")}catch{}e.streams.delete(n)}if(e.ws){e.ws.removeAllListeners();try{e.ws.close(1e3,"tunnel client stopped")}catch{}e.ws=void 0}}function y(e){if(e.shuttingDown)return;const n=e.instanceId;if(!n)return;const r=L(e);if(!r){g(e);return}c.info("[TunnelClient] \u8FDE\u63A5\u96A7\u9053\u670D\u52A1\u7AEF:",r.url);const t=new m(r.url,{handshakeTimeout:15e3,headers:{"X-Runtime-Token":r.token}});e.ws=t,t.on("open",()=>{c.info("[TunnelClient] \u96A7\u9053\u8FDE\u63A5\u5DF2\u5EFA\u7ACB: server=",e.serverUrl,"instanceId=",n),e.reconnectTimer&&clearTimeout(e.reconnectTimer),e.reconnectTimer=void 0,e.timer=setInterval(()=>{t.readyState===m.OPEN&&t.send(JSON.stringify({t:"ping"}))},A)}),t.on("unexpected-response",(o,i)=>{c.error("[TunnelClient] \u670D\u52A1\u7AEF\u62D2\u7EDD\u8FDE\u63A5\uFF08\u975E 101 \u54CD\u5E94\uFF09: server=",e.serverUrl,"statusCode=",i.statusCode,"statusMessage=",i.statusMessage)}),t.on("upgrade",o=>{c.debug("[TunnelClient] \u6536\u5230\u5347\u7EA7\u54CD\u5E94: statusCode=",o.statusCode)}),t.on("message",o=>{let i;typeof o=="string"?i=o:Buffer.isBuffer(o)?i=o.toString("utf-8"):i=Buffer.from(o).toString("utf-8");try{const l=JSON.parse(i);R(e,l)}catch(l){c.warn("[TunnelClient] \u89E3\u6790\u96A7\u9053\u6D88\u606F\u5931\u8D25:",String(l))}}),t.on("close",(o,i)=>{if(c.warn("[TunnelClient] \u96A7\u9053\u8FDE\u63A5\u5173\u95ED: server=",e.serverUrl,"code=",o,"reason=",String(i)),C(e),o===M){c.warn("[TunnelClient] \u9274\u6743\u5931\u8D25\uFF084001\uFF09\uFF0C\u505C\u6B62\u81EA\u52A8\u91CD\u8FDE\uFF1B\u5F85\u91CD\u65B0\u6CE8\u518C\u540E\u518D\u542F\u52A8\u96A7\u9053: server=",e.serverUrl);return}g(e)}),t.on("error",o=>{c.warn("[TunnelClient] \u96A7\u9053\u8FDE\u63A5\u9519\u8BEF: server=",e.serverUrl,"err=",o.message,"code=",o.code),C(e),g(e)})}function C(e){e.timer&&(clearInterval(e.timer),e.timer=void 0);for(const[n,r]of e.streams.entries()){try{r.ws.close(1011,"tunnel disconnected")}catch{}e.streams.delete(n)}e.ws=void 0}function g(e){e.shuttingDown||e.reconnectTimer||(e.reconnectTimer=setTimeout(()=>{e.reconnectTimer=void 0,y(e)},I))}function R(e,n){switch(n.t){case"req":D(e,n);break;case"wsopen":W(e,n);break;case"wsmsg":x(e,n);break;case"wsclose":P(e,n);break;case"ping":d(e,{t:"pong"});break;case"pong":break;default:c.debug("[TunnelClient] \u672A\u77E5\u6D88\u606F\u7C7B\u578B: t=",n.t)}}async function D(e,n){const r=n.id,t=n.method||"GET",o=n.path||"/",i=`http://127.0.0.1:${T}${o}`;try{const l=n.body?Buffer.from(n.body,"base64"):void 0,s={};if(n.headers)for(const[p,f]of Object.entries(n.headers))f&&f.length>0&&(s[p]=f[0]);s["x-bridge-tunnel-source"]=e.serverUrl;const u=await k.request({url:i,method:t,headers:s,data:l,timeout:12e4,responseType:"arraybuffer",validateStatus:()=>!0}),a={};for(const[p,f]of Object.entries(u.headers))f!==void 0&&(a[p]=Array.isArray(f)?f:[String(f)]);const v=Buffer.isBuffer(u.data)?u.data.toString("base64"):"";d(e,{t:"res",id:r,status:u.status,headers:a,body:v||null})}catch(l){const s=l instanceof U?l.message:String(l);c.warn("[TunnelClient] \u672C\u5730\u8F6C\u53D1\u5931\u8D25:",t,o,"->",s),d(e,{t:"res",id:r,status:502,headers:{"content-type":["text/plain; charset=utf-8"]},body:Buffer.from(s).toString("base64")})}}function W(e,n){const r=n.id,t=n.path||"/",o=`ws://127.0.0.1:${T}${t}`;let i;try{i=new m(o,{handshakeTimeout:1e4})}catch(s){c.warn("[TunnelClient] \u672C\u5730 WS \u8FDE\u63A5\u5931\u8D25:",String(s)),d(e,{t:"wsclose",id:r,code:1011,reason:"local ws connect failed"});return}const l={ws:i,pending:[]};e.streams.set(r,l),i.on("open",()=>{for(const s of l.pending)b(l,s.data,s.bin);l.pending=[],d(e,{t:"wsopened",id:r})}),i.on("message",(s,u)=>{const a=Buffer.isBuffer(s)?s:Buffer.from(s);u&&e.ws&&e.ws.bufferedAmount>O||d(e,{t:"wsmsg",id:r,bin:u,d:a.toString("base64")})}),i.on("close",(s,u)=>{e.streams.delete(r),d(e,{t:"wsclose",id:r,code:s,reason:String(u)})}),i.on("error",s=>{c.warn("[TunnelClient] \u672C\u5730 WS \u9519\u8BEF: id=",r,"err=",s.message),e.streams.delete(r);try{i.close(1011,s.message)}catch{}d(e,{t:"wsclose",id:r,code:1011,reason:s.message})})}function b(e,n,r){r?e.ws.send(n):e.ws.send(n.toString("utf-8"))}function x(e,n){const r=e.streams.get(n.id);if(!r)return;const t=n.d?Buffer.from(n.d,"base64"):Buffer.alloc(0);if(r.ws.readyState!==m.OPEN){r.pending.length>=N&&(r.pending.shift(),c.warn("[TunnelClient] \u672C\u5730 WS \u672A\u5C31\u7EEA\uFF0C\u6392\u961F\u6D88\u606F\u8D85\u4E0A\u9650\uFF0C\u4E22\u5F03\u6700\u65E7\u4E00\u6761: id=",n.id)),r.pending.push({data:t,bin:!!n.bin});return}b(r,t,!!n.bin)}function P(e,n){const r=e.streams.get(n.id);if(r){e.streams.delete(n.id);try{r.ws.close(n.code??1e3,n.reason||"")}catch{}}}function d(e,n){if(!(!e.ws||e.ws.readyState!==m.OPEN))try{e.ws.send(JSON.stringify(n))}catch(r){c.warn("[TunnelClient] sendJson \u5931\u8D25:",String(r))}}export{j as startTunnelClient,z as stopTunnelClient};
|
|
2
2
|
|