aws-runtime-bridge 1.9.128 → 1.9.131
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-frame-encode.d.ts +16 -0
- package/dist/browser/browser-frame-encode.js +2 -0
- package/dist/browser/browser-pool.js +1 -1
- package/dist/browser/screencast-differ.d.ts +35 -0
- package/dist/browser/screencast-differ.js +2 -0
- package/dist/browser/screencast-manager.d.ts +32 -0
- package/dist/browser/screencast-manager.js +1 -1
- package/dist/browser/types.d.ts +48 -1
- package/dist/browser/types.js +1 -1
- package/dist/browser/ws-server.d.ts +1 -1
- package/dist/browser/ws-server.js +1 -1
- package/dist/routes/background-tasks.js +7 -1
- package/dist/routes/instance-tool-status.d.ts +4 -0
- package/dist/routes/instance-tool-status.js +2 -0
- package/dist/routes/instance.d.ts +0 -2
- package/dist/routes/instance.js +1 -1
- package/dist/services/acode-bg-manager.d.ts +36 -0
- package/dist/services/acode-bg-manager.js +2 -0
- package/dist/services/browser-session-manager.js +1 -1
- package/package/acode/dist/await-complete-tool.js +4 -4
- package/package/acode/dist/background-command-manager.d.ts +58 -0
- package/package/acode/dist/background-command-manager.js +5 -6
- package/package/acode/dist/background-command-tools.js +1 -1
- package/package/acode/dist/built-in-file-tools.js +2 -2
- package/package/acode/dist/builtins/shellCommandResult.d.ts +3 -0
- package/package/acode/dist/builtins/shellCommandResult.js +1 -1
- package/package/acode/dist/builtins/shellCommandRunner.d.ts +2 -0
- package/package/acode/dist/builtins/shellCommandRunner.js +1 -1
- package/package/acode/dist/runtime.js +7 -7
- package/package/acode/dist/sub-agent-tools.js +30 -26
- package/package/acode/dist/sub-agent-types.d.ts +1 -1
- package/package/acode/dist/tool-allocation.d.ts +4 -4
- package/package/acode/dist/tool-allocation.js +1 -1
- package/package/aws-client-agent-mcp/dist/mcp-server.js +1 -1
- package/package.json +3 -3
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
|
|
2
|
+
import type { ScreencastDiffFrame } from "./types.js";
|
|
3
|
+
export declare const BROWSER_FRAME_MAGIC = 16979;
|
|
4
|
+
export declare const BROWSER_FRAME_VERSION = 1;
|
|
5
|
+
export declare const BROWSER_FRAME_FLAG_TILE = 1;
|
|
6
|
+
export declare const BROWSER_FRAME_FLAG_FULL = 2;
|
|
7
|
+
|
|
8
|
+
export declare const BROWSER_FRAME_FLAG_META = 4;
|
|
9
|
+
|
|
10
|
+
export interface BrowserFrameMeta {
|
|
11
|
+
|
|
12
|
+
url?: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export declare function isIncrementalBinaryFrame(buf: Buffer | undefined | null): boolean;
|
|
16
|
+
export declare function encodeBrowserFrame(frame: ScreencastDiffFrame, seq: number, meta?: BrowserFrameMeta): Buffer;
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
const f=16979,u=1,a=1,A=2,F=4;function s(t){const i=t?.url?.trim();if(!i)return null;try{const r=Buffer.from(JSON.stringify({url:i}),"utf-8");if(r.length===0||r.length>65535)return null;const e=Buffer.allocUnsafe(2+r.length);return e.writeUInt16BE(r.length,0),r.copy(e,2),e}catch{return null}}function h(t){return!t||t.length<4?!1:t.readUInt16BE(0)===16979&&(t[3]&1)!==0&&(t[3]&2)===0}function d(t,i,r){t.kind==="tile"&&t.tiles.length===0&&(t={kind:"full",width:t.width,height:t.height,data:Buffer.alloc(0)});const e=s(r),B=(t.kind==="full"?2:1)|(e?4:0),_=8+(e?e.length:0),E=Buffer.allocUnsafe(_);if(E.writeUInt16BE(16979,0),E[2]=1,E[3]=B,E.writeUInt32BE(i>>>0,4),e&&e.copy(E,8),t.kind==="full"){const n=Buffer.allocUnsafe(8);return n.writeUInt32BE(t.width,0),n.writeUInt32BE(t.height,4),Buffer.concat([E,n,t.data])}const R=t.tiles.length;let c=0;for(const n of t.tiles)c+=n.data.length;const o=Buffer.allocUnsafe(2+R*12+c);o.writeUInt16BE(R,0);let l=2;for(const n of t.tiles)o.writeUInt16BE(n.x,l),o.writeUInt16BE(n.y,l+2),o.writeUInt16BE(n.w,l+4),o.writeUInt16BE(n.h,l+6),o.writeUInt32BE(n.data.length,l+8),l+=12;for(const n of t.tiles)n.data.copy(o,l),l+=n.data.length;return Buffer.concat([E,o])}export{A as BROWSER_FRAME_FLAG_FULL,F as BROWSER_FRAME_FLAG_META,a as BROWSER_FRAME_FLAG_TILE,f as BROWSER_FRAME_MAGIC,u as BROWSER_FRAME_VERSION,d as encodeBrowserFrame,h as isIncrementalBinaryFrame};
|
|
2
|
+
|
|
@@ -1,2 +1,2 @@
|
|
|
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)}getSsrfConfig(){return this.config.ssrfConfig}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};
|
|
1
|
+
import{chromium as x}from"playwright";import{formatBrowserLaunchError as _,installPlaywrightChromium as $,isBrowserMissingError as D}from"./browser-bootstrap.js";import{BrowserProfileStore as H}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 B}from"./screencast-manager.js";import{buildStealthInitScript as L,STEALTH_LAUNCH_ARGS as O,STEALTH_IGNORE_ARGS as N}from"./stealth.js";import{StepArchive as R}from"./step-archive.js";import{DEFAULT_BROWSER_ID as l,IDLE_CHECK_INTERVAL_MS as K,IDLE_TIMEOUT_MS as z,MAX_PAGES_PER_SESSION as U,MAX_SESSIONS as I,MAX_SESSIONS_PER_AGENT as M,SESSION_TIMEOUT_MS as W,makeBrowserKey as G}from"./types.js";const q=1280,V=720;class X{onSessionClose(e){this.sessionCloseHandlers.push(e)}getSsrfConfig(){return this.config.ssrfConfig}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():l;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??q,viewportHeight:e?.viewportHeight??V,maxPagesPerSession:e?.maxPagesPerSession??U,maxSessionDurationMs:e?.maxSessionDurationMs??W,idleTimeoutMs:e?.idleTimeoutMs??z,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 H({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??l}`);if(this.sessions.size>=I)throw new Error(`BrowserPool reached MAX_SESSIONS=${I}; close existing sessions before launching new ones`);let c=0;for(const o of this.sessions.values())o.agentId===e&&(c+=1);if(c>=M)throw new Error(`BrowserPool reached MAX_SESSIONS_PER_AGENT=${M} for agent ${e}; close some browser sessions first`);const h={headless:this.config.headless};this.config.executablePath&&(h.executablePath=this.config.executablePath);const d=[`--window-size=${this.config.viewportWidth},${this.config.viewportHeight}`];this.config.stealth?(h.ignoreDefaultArgs=[...N],h.args=[...d,...O,...this.config.launchArgs??[]]):this.config.launchArgs&&this.config.launchArgs.length>0?h.args=[...d,...this.config.launchArgs]:h.args=[...d];const p=await this.launchChromium(h);let m=this.config.userAgent,P;if(this.config.stealth)try{const o=await p.newBrowserCDPSession(),f=await o.send("Browser.getVersion"),g=f.product?.match(/Chrome\/(\d+)/);g&&(P=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(L(P??"126")).catch(()=>{});const S=await v.newPage().catch(async o=>{throw await p.close().catch(()=>{}),o}),A=new B(this.config.screencastConfig),C=new R(this.config.archiveConfig),T=new k(e,s?.trim()||l,{onTrigger:(o,f)=>{const g=this.listenerTriggerHook;if(g)try{g(e,s?.trim()||l,o,f)}catch{}},store:this.listenerStore,scopeKey:r});try{await A.start(S)}catch{}try{const o=S;o.screencast?.showActions&&await o.screencast.showActions({cursor:"pointer"})}catch{}const E=Date.now(),a={agentId:e,scopeKey:r,browserId:s?.trim()||l,title:i?.trim()||"\u6D4F\u89C8\u5668\u4F1A\u8BDD",browser:p,context:v,pages:[S],activePageIndex:0,screencast:A,archive:C,listenerManager:T,launchedAt:E,lastActivityAt:E,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 T.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}async launchChromium(e){try{return await x.launch(e)}catch(s){if(this.config.executablePath||!D(s))throw s;const i=await $();try{return await x.launch(e)}catch(t){throw _(t instanceof Error?t:new Error(String(t)),i)}}}get(e,s){const i=s&&s.trim()?s.trim():l;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():l;let t,r;for(const[c,h]of this.sessions)if(h.agentId===e&&h.browserId===i){t=c,r=h;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():l;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():l;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??l}`)}ensureIdleTimer(){this.idleTimer||this.config.idleTimeoutMs<=0&&this.config.maxSessionDurationMs<=0||(this.idleTimer=setInterval(()=>{this.checkIdleSessions()},K),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 ie(y){return u||(u=new X(y)),u}async function re(){u&&(await u.dispose(),u=null)}export{X as BrowserPool,re as disposeBrowserPool,ie as getBrowserPool};
|
|
2
2
|
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
|
|
2
|
+
import { type ScreencastDiffFrame } from "./types.js";
|
|
3
|
+
|
|
4
|
+
export declare function getSharpLoadStatus(): string;
|
|
5
|
+
export declare class ScreencastDiffer {
|
|
6
|
+
|
|
7
|
+
private lastRaw;
|
|
8
|
+
private lastWidth;
|
|
9
|
+
private lastHeight;
|
|
10
|
+
|
|
11
|
+
private lastJpeg;
|
|
12
|
+
|
|
13
|
+
private lastJpegWidth;
|
|
14
|
+
private lastJpegHeight;
|
|
15
|
+
|
|
16
|
+
private consecutiveFullFrames;
|
|
17
|
+
|
|
18
|
+
private highDynamicMode;
|
|
19
|
+
|
|
20
|
+
private highDynamicFrameCount;
|
|
21
|
+
|
|
22
|
+
private sharpReady;
|
|
23
|
+
private sharpCheckDone;
|
|
24
|
+
constructor();
|
|
25
|
+
|
|
26
|
+
isSharpReady(): boolean;
|
|
27
|
+
|
|
28
|
+
processFrame(jpegBuffer: Buffer, width: number, height: number, quality: number): Promise<ScreencastDiffFrame | null>;
|
|
29
|
+
|
|
30
|
+
private emitFullFrame;
|
|
31
|
+
|
|
32
|
+
reset(): void;
|
|
33
|
+
|
|
34
|
+
dispose(): void;
|
|
35
|
+
}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{TILE_DIFF_CONFIG as E}from"./types.js";const _=3,I=15;let R=null,S="pending",W=null;async function b(){return R||(W||(W=(async()=>{try{const s=await import("sharp");return R=s.default??s,S="ok",R}catch(s){return S="failed",console.warn("[browser-screencast] sharp \u52A0\u8F7D\u5931\u8D25\uFF0C\u964D\u7EA7\u5168\u5E27\u6A21\u5F0F:",s instanceof Error?s.message:s),null}})()),W)}async function k(){return b()}function P(){return S}function A(s,t,a,e,f,m,o,d){if(!t||t.length!==s.length||a<=0||e<=0)return Number.MAX_SAFE_INTEGER;const h=Math.max(0,f),r=Math.max(0,m),c=Math.min(a,f+o),M=Math.min(e,m+d),H=a*4,{sampleStep:w,diffThreshold:p}=E;let y=0;for(let J=r;J<M;J+=w){const C=J*H;for(let x=h;x<c;x+=w){const F=C+x*4;Math.abs(s[F]-t[F])+Math.abs(s[F+1]-t[F+1])+Math.abs(s[F+2]-t[F+2])>p&&y++}}return y}function N(s,t,a,e,f,m){if(!s||t<=0||f<=0||m<=0)return null;const o=Math.max(0,a),d=Math.max(0,e),h=Math.min(t,a+f),r=Math.min(Math.floor(s.length/4/t),e+m),c=h-o,M=r-d;if(c<=0||M<=0)return null;const H=t*4,w=Buffer.allocUnsafe(c*M*4);for(let p=0;p<M;p++){const y=(d+p)*H+o*4;s.copy(w,p*c*4,y,y+c*4)}return w}async function O(s,t,a){const e=new Array(s.length);let f=0;const m=Math.max(1,Math.min(t,s.length));return await Promise.all(Array.from({length:m},async()=>{for(;;){const o=f++;if(o>=s.length)return;e[o]=await a(s[o])}})),e}class Y{constructor(){this.lastRaw=null,this.lastWidth=0,this.lastHeight=0,this.lastJpeg=null,this.lastJpegWidth=0,this.lastJpegHeight=0,this.consecutiveFullFrames=0,this.highDynamicMode=!1,this.highDynamicFrameCount=0,this.sharpReady=!1,this.sharpCheckDone=!1,k().then(t=>{this.sharpReady=t!==null,this.sharpCheckDone=!0})}isSharpReady(){return this.sharpReady}async processFrame(t,a,e,f){if(!this.sharpCheckDone||!this.sharpReady)return this.emitFullFrame(t,a,e);const m=R;if(!m)return this.emitFullFrame(t,a,e);try{if(this.lastRaw&&this.lastJpeg&&this.lastJpegWidth===a&&this.lastJpegHeight===e&&this.lastJpeg.length===t.length&&this.lastJpeg.equals(t))return null;if(this.highDynamicMode)if(this.highDynamicFrameCount++,this.highDynamicFrameCount<I&&!(this.lastJpegWidth===a&&this.lastJpegHeight===e))this.highDynamicMode=!1;else{if(this.highDynamicFrameCount<I)return this.lastRaw=null,this.lastJpeg=t,this.lastJpegWidth=a,this.lastJpegHeight=e,this.emitFullFrame(t,this.lastWidth||a,this.lastHeight||e);this.highDynamicMode=!1,this.highDynamicFrameCount=0}const o=await m(t).ensureAlpha().raw().toBuffer({resolveWithObject:!0}),d=o.data,h=o.info.width,r=o.info.height;if(!this.lastRaw||this.lastWidth!==h||this.lastHeight!==r)return this.lastRaw=d,this.lastWidth=h,this.lastHeight=r,this.lastJpeg=t,this.lastJpegWidth=a,this.lastJpegHeight=e,this.emitFullFrame(t,h,r);const{tileSize:c,minChangedSamples:M,fullFrameFallbackRatio:H,encodeConcurrency:w}=E,p=Math.max(1,Math.ceil(h/c)),y=Math.max(1,Math.ceil(r/c)),J=p*y,C=[];let x=0;for(let i=0;i<y;i++)for(let u=0;u<p;u++){const l=u*c,n=i*c,g=Math.min(c,h-l),D=Math.min(c,r-n);A(d,this.lastRaw,h,r,l,n,g,D)>=M&&(C.push({x:l,y:n,w:g,h:D}),x++)}if(x===0)return null;if(x>=Math.ceil(J*H))return this.lastRaw=d,this.lastWidth=h,this.lastHeight=r,this.lastJpeg=t,this.lastJpegWidth=a,this.lastJpegHeight=e,this.consecutiveFullFrames++,this.consecutiveFullFrames>=_&&(this.highDynamicMode=!0,this.highDynamicFrameCount=0),this.emitFullFrame(t,h,r);this.consecutiveFullFrames=0,this.highDynamicMode=!1,this.highDynamicFrameCount=0;const F=[];for(let i=0;i<y;i++){let u=0;const l=C.filter(n=>Math.floor(n.y/c)===i);for(l.sort((n,g)=>n.x-g.x);u<l.length;){let n=u;for(;n+1<l.length&&l[n+1].x===l[n].x+l[n].w;)n++;const g=l[u],D=l[n];F.push({x:g.x,y:g.y,w:D.x+D.w-g.x,h:g.h}),u=n+1}}const T=(await O(F,w,async i=>{const u=Math.min(i.w,h-i.x),l=Math.min(i.h,r-i.y),n=N(d,h,i.x,i.y,u,l);if(!n)return null;try{const g=await m(n,{raw:{width:u,height:l,channels:4}}).jpeg({quality:f}).toBuffer();return{x:i.x,y:i.y,w:u,h:l,data:g}}catch{return null}})).filter(i=>i!==null);return T.length===0?null:(this.lastRaw=d,this.lastWidth=h,this.lastHeight=r,this.lastJpeg=t,this.lastJpegWidth=a,this.lastJpegHeight=e,{kind:"tile",width:h,height:r,tiles:T})}catch(o){return console.warn("[browser-screencast] tile diff \u5F02\u5E38\uFF0C\u56DE\u9000\u5168\u5E27:",o),this.emitFullFrame(t,a,e)}}emitFullFrame(t,a,e){return{kind:"full",width:a,height:e,data:t}}reset(){this.lastRaw=null,this.lastWidth=0,this.lastHeight=0,this.lastJpeg=null,this.lastJpegWidth=0,this.lastJpegHeight=0,this.consecutiveFullFrames=0,this.highDynamicMode=!1,this.highDynamicFrameCount=0}dispose(){this.reset()}}export{Y as ScreencastDiffer,P as getSharpLoadStatus};
|
|
2
|
+
|
|
@@ -14,7 +14,31 @@ export declare class ScreencastManager {
|
|
|
14
14
|
private readonly subscribers;
|
|
15
15
|
|
|
16
16
|
private lastFrame;
|
|
17
|
+
|
|
18
|
+
private differ;
|
|
19
|
+
|
|
20
|
+
private tileMode;
|
|
21
|
+
|
|
22
|
+
private frameSeq;
|
|
23
|
+
|
|
24
|
+
private pipeline;
|
|
25
|
+
|
|
26
|
+
private latestEvent;
|
|
27
|
+
|
|
28
|
+
private draining;
|
|
29
|
+
|
|
30
|
+
private lastProcessStartedAt;
|
|
31
|
+
|
|
32
|
+
private readonly frameIntervalMs;
|
|
33
|
+
|
|
34
|
+
private seedCaptureInFlight;
|
|
35
|
+
|
|
36
|
+
private lastDeviceWidth;
|
|
37
|
+
private lastDeviceHeight;
|
|
38
|
+
|
|
39
|
+
private lastGeometryWarnAt;
|
|
17
40
|
constructor(config?: ScreencastConfig);
|
|
41
|
+
private enqueue;
|
|
18
42
|
|
|
19
43
|
subscribe(fn: FrameSubscriber): () => void;
|
|
20
44
|
|
|
@@ -22,6 +46,12 @@ export declare class ScreencastManager {
|
|
|
22
46
|
|
|
23
47
|
private handleScreencastFrame;
|
|
24
48
|
|
|
49
|
+
private drainPendingFrames;
|
|
50
|
+
|
|
51
|
+
private ackFrame;
|
|
52
|
+
|
|
53
|
+
private processScreencastFrame;
|
|
54
|
+
|
|
25
55
|
stop(page: Page): Promise<void>;
|
|
26
56
|
|
|
27
57
|
stopAll(): Promise<void>;
|
|
@@ -38,5 +68,7 @@ export declare class ScreencastManager {
|
|
|
38
68
|
|
|
39
69
|
forceCaptureAndPush(page: Page): Promise<void>;
|
|
40
70
|
|
|
71
|
+
private doForceCaptureAndPush;
|
|
72
|
+
|
|
41
73
|
dispose(): Promise<void>;
|
|
42
74
|
}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{QUALITY_PRESETS as
|
|
1
|
+
import{encodeBrowserFrame as d,isIncrementalBinaryFrame as m}from"./browser-frame-encode.js";import{ScreencastDiffer as g}from"./screencast-differ.js";import{QUALITY_PRESETS as y}from"./types.js";const l=y.original,F=1,p="jpeg",w=30;class x{constructor(t){this.currentPage=null,this.currentSession=null,this.isRunning=!1,this.subscribers=new Set,this.differ=null,this.tileMode=!0,this.frameSeq=0,this.pipeline=Promise.resolve(),this.latestEvent=null,this.draining=!1,this.lastProcessStartedAt=0,this.seedCaptureInFlight=!1,this.lastDeviceWidth=0,this.lastDeviceHeight=0,this.lastGeometryWarnAt=0,this.config={quality:t?.quality??l.quality,maxWidth:t?.maxWidth??l.maxWidth,maxHeight:t?.maxHeight??l.maxHeight,everyNthFrame:t?.everyNthFrame??F,maxFramesPerSecond:t?.maxFramesPerSecond??w,format:t?.format??p,tileMode:t?.tileMode??!0},this.tileMode=this.config.tileMode,this.frameIntervalMs=1e3/Math.max(1,this.config.maxFramesPerSecond),this.tileMode&&(this.differ=new g)}enqueue(t){const e=this.pipeline.then(t);return this.pipeline=e.then(()=>{},()=>{}),e}subscribe(t){if(this.subscribers.add(t),this.subscribers.size===1&&this.currentPage&&!this.isRunning&&this.start(this.currentPage).catch(()=>{}),this.differ&&this.currentPage){let e=!1;if(this.lastFrame?.binaryFrame?m(this.lastFrame.binaryFrame)&&(this.lastFrame=void 0,this.differ.reset(),e=!0):this.tileMode&&(this.differ.reset(),e=!0),e&&!this.seedCaptureInFlight){const s=this.currentPage;this.seedCaptureInFlight=!0,this.forceCaptureAndPush(s).catch(()=>{}).finally(()=>{this.seedCaptureInFlight=!1})}}if(this.lastFrame){const e=this.lastFrame;setImmediate(()=>{if(this.subscribers.has(t))try{t(e)}catch{}})}return()=>{this.subscribers.delete(t)}}async start(t){if(this.currentPage===t&&this.isRunning)return;this.currentPage&&this.currentPage!==t&&await this.stop(this.currentPage),this.currentPage=t;let e;try{e=await t.context().newCDPSession(t)}catch(s){throw this.isRunning=!1,new Error(`Failed to create CDPSession for screencast: ${s instanceof Error?s.message:String(s)}`)}this.currentSession=e,e.on("Page.screencastFrame",s=>{this.handleScreencastFrame(s,t)});try{await e.send("Page.startScreencast",{format:this.config.format,quality:this.config.quality,maxWidth:this.config.maxWidth,maxHeight:this.config.maxHeight,everyNthFrame:this.config.everyNthFrame,...this.config.maxFramesPerSecond?{maxFramesPerSecond:this.config.maxFramesPerSecond}:{}}),this.isRunning=!0}catch(s){try{await e.detach()}catch{}throw this.currentSession=null,this.isRunning=!1,new Error(`Failed to start screencast: ${s instanceof Error?s.message:String(s)}`)}}handleScreencastFrame(t,e){if(this.ackFrame(t),e===this.currentPage){if(this.subscribers.size===0){this.lastFrame=void 0;return}this.latestEvent={event:t,page:e},this.draining||(this.draining=!0,this.drainPendingFrames())}}async drainPendingFrames(){try{for(;this.latestEvent;){const{event:t,page:e}=this.latestEvent;this.latestEvent=null;const s=this.frameIntervalMs-(Date.now()-this.lastProcessStartedAt);s>0&&await new Promise(i=>setTimeout(i,s)),this.lastProcessStartedAt=Date.now(),await this.enqueue(()=>this.processScreencastFrame(t,e))}}catch{}finally{this.draining=!1}}ackFrame(t){this.currentSession&&t.sessionId&&this.currentSession.send("Page.screencastFrameAck",{sessionId:t.sessionId}).catch(()=>{})}async processScreencastFrame(t,e){if(e!==this.currentPage)return;const s=t.data??"",i=t.metadata?.deviceWidth??0,r=t.metadata?.deviceHeight??0;let n="";try{n=e.url()}catch{}if(i>0&&r>0&&this.lastDeviceWidth>0&&this.lastDeviceHeight>0&&(i!==this.lastDeviceWidth||r!==this.lastDeviceHeight)){this.differ?.reset();const a=Date.now();a-this.lastGeometryWarnAt>=1e3&&(this.lastGeometryWarnAt=a,console.warn(`[browser-screencast] \u9875\u9762\u51E0\u4F55\u53D8\u5316 ${this.lastDeviceWidth}x${this.lastDeviceHeight} -> ${i}x${r}\uFF0C\u91CD\u7F6E\u5DEE\u5206\u57FA\u51C6`))}if(i>0&&r>0&&(this.lastDeviceWidth=i,this.lastDeviceHeight=r),this.tileMode&&this.differ&&s){const a=Buffer.from(s,"base64"),c=await this.differ.processFrame(a,i,r,this.config.quality);if(!c)return;const f=d(c,this.frameSeq++,{url:n}),o={dataUrl:"",base64:"",width:i,height:r,timestamp:Date.now(),url:n,binaryFrame:f};this.lastFrame=o;for(const u of this.subscribers)try{u(o)}catch{}return}const h={dataUrl:`data:image/${this.config.format==="png"?"png":"jpeg"};base64,${s}`,base64:s,width:i,height:r,timestamp:Date.now(),url:n};this.lastFrame=h;for(const a of this.subscribers)try{a(h)}catch{}}async stop(t){if(this.currentPage!==t)return;const e=this.currentSession;if(e){try{await e.send("Page.stopScreencast")}catch{}try{await e.detach()}catch{}}this.currentSession=null,this.isRunning=!1,this.currentPage=null,this.latestEvent=null,this.differ?.reset()}async stopAll(){this.currentPage&&await this.stop(this.currentPage)}async capture(t){if(this.lastFrame&&this.currentPage===t)return this.lastFrame;try{const s=(await t.screenshot({type:this.config.format==="png"?"png":"jpeg",quality:this.config.format==="png"?void 0:this.config.quality})).toString("base64"),i=`data:image/${this.config.format==="png"?"png":"jpeg"};base64,${s}`,r=t.viewportSize();return{dataUrl:i,base64:s,width:r?.width??0,height:r?.height??0,timestamp:Date.now(),url:t.url()}}catch{return}}getLastFrame(){return this.lastFrame}getCurrentCdpSession(){return this.currentSession??void 0}async restartAfterNavigation(t){if(this.currentPage===t)try{await this.stop(t)}catch{}let e=!1;for(let s=0;s<2;s++)try{await this.start(t),e=!0;break}catch{s===0&&await new Promise(i=>setTimeout(i,300))}e||(this.isRunning=!1,this.currentSession=null,this.currentPage=null),await this.forceCaptureAndPush(t).catch(()=>{})}async updateQuality(t){this.config={...this.config,quality:t.quality,maxWidth:t.maxWidth,maxHeight:t.maxHeight};const e=this.currentPage;if(!e||!this.isRunning)return!1;try{await this.stop(e)}catch{}try{await this.start(e)}catch{return!1}return this.forceCaptureAndPush(e).catch(()=>{}),!0}async forceCaptureAndPush(t){!t||t.isClosed()||await this.enqueue(()=>this.doForceCaptureAndPush(t))}async doForceCaptureAndPush(t){let e;const s=t.viewportSize(),i=s?.width??0,r=s?.height??0;if(this.currentSession)try{e=(await this.currentSession.send("Page.captureScreenshot",{format:this.config.format==="png"?"png":"jpeg",quality:this.config.format==="png"?void 0:this.config.quality})).data}catch{}if(!e)try{e=(await t.screenshot({type:this.config.format==="png"?"png":"jpeg",quality:this.config.format==="png"?void 0:this.config.quality})).toString("base64")}catch{return}let n="";try{n=t.url()}catch{}if(this.tileMode&&this.differ&&e){this.differ.reset();const a=Buffer.from(e,"base64"),c=await this.differ.processFrame(a,i,r,this.config.quality);if(c){const f=d(c,this.frameSeq++,{url:n}),o={dataUrl:"",base64:"",width:i,height:r,timestamp:Date.now(),url:n,binaryFrame:f};this.lastFrame=o;for(const u of this.subscribers)try{u(o)}catch{}return}}const h={dataUrl:`data:image/${this.config.format==="png"?"png":"jpeg"};base64,${e}`,base64:e,width:i,height:r,timestamp:Date.now(),url:n};this.lastFrame=h;for(const a of this.subscribers)try{a(h)}catch{}}async dispose(){await this.stopAll(),this.latestEvent=null,this.differ?.dispose(),this.differ=null,this.subscribers.clear(),this.lastFrame=void 0}}export{x as ScreencastManager};
|
|
2
2
|
|
package/dist/browser/types.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
|
|
2
2
|
import type { Browser, BrowserContext, Page } from "playwright";
|
|
3
|
+
import type { PageListenerManager } from "./page-listener-manager.js";
|
|
3
4
|
import type { ScreencastManager } from "./screencast-manager.js";
|
|
4
5
|
import type { StepArchive } from "./step-archive.js";
|
|
5
|
-
import type { PageListenerManager } from "./page-listener-manager.js";
|
|
6
6
|
|
|
7
7
|
export declare const DEFAULT_BROWSER_ID = "default";
|
|
8
8
|
|
|
@@ -27,6 +27,8 @@ export interface ScreencastFrame {
|
|
|
27
27
|
timestamp: number;
|
|
28
28
|
|
|
29
29
|
url: string;
|
|
30
|
+
|
|
31
|
+
binaryFrame?: Buffer;
|
|
30
32
|
}
|
|
31
33
|
|
|
32
34
|
export type FrameSubscriber = (frame: ScreencastFrame) => void;
|
|
@@ -88,6 +90,8 @@ export interface ScreencastConfig {
|
|
|
88
90
|
maxFramesPerSecond?: number;
|
|
89
91
|
|
|
90
92
|
format?: "jpeg" | "png";
|
|
93
|
+
|
|
94
|
+
tileMode?: boolean;
|
|
91
95
|
}
|
|
92
96
|
|
|
93
97
|
export interface StepArchiveConfig {
|
|
@@ -190,3 +194,46 @@ export interface QualityPresetConfig {
|
|
|
190
194
|
export declare const QUALITY_PRESETS: Record<QualityPreset, QualityPresetConfig>;
|
|
191
195
|
|
|
192
196
|
export declare function isQualityPreset(value: unknown): value is QualityPreset;
|
|
197
|
+
|
|
198
|
+
export declare const TILE_DIFF_CONFIG: {
|
|
199
|
+
|
|
200
|
+
readonly tileSize: 160;
|
|
201
|
+
|
|
202
|
+
readonly sampleStep: 8;
|
|
203
|
+
|
|
204
|
+
readonly diffThreshold: 24;
|
|
205
|
+
|
|
206
|
+
readonly minChangedSamples: 1;
|
|
207
|
+
|
|
208
|
+
readonly fullFrameFallbackRatio: 0.5;
|
|
209
|
+
|
|
210
|
+
readonly encodeConcurrency: 8;
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
export interface ScreencastTileFrame {
|
|
214
|
+
kind: "tile";
|
|
215
|
+
|
|
216
|
+
width: number;
|
|
217
|
+
|
|
218
|
+
height: number;
|
|
219
|
+
|
|
220
|
+
tiles: Array<{
|
|
221
|
+
x: number;
|
|
222
|
+
y: number;
|
|
223
|
+
w: number;
|
|
224
|
+
h: number;
|
|
225
|
+
data: Buffer;
|
|
226
|
+
}>;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
export interface ScreencastFullFrame {
|
|
230
|
+
kind: "full";
|
|
231
|
+
|
|
232
|
+
width: number;
|
|
233
|
+
|
|
234
|
+
height: number;
|
|
235
|
+
|
|
236
|
+
data: Buffer;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
export type ScreencastDiffFrame = ScreencastTileFrame | ScreencastFullFrame;
|
package/dist/browser/types.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const
|
|
1
|
+
const e="default";function r(t,o){return`${t}::${o}`}function n(t){const o=t.indexOf("::");return o<0?null:{agentId:t.slice(0,o),browserId:t.slice(o+2)}}const S=8,_=4,i=5,s=0,E=0,x=7891,c="127.0.0.1",a=2*1024*1024,p=10*1e3,T=30*1e3,I=60*1e3,l={original:{quality:80,maxWidth:1280,maxHeight:800},hd:{quality:60,maxWidth:1024,maxHeight:640},smooth:{quality:45,maxWidth:800,maxHeight:500}};function d(t){return t==="original"||t==="hd"||t==="smooth"}const A={tileSize:160,sampleStep:8,diffThreshold:24,minChangedSamples:1,fullFrameFallbackRatio:.5,encodeConcurrency:8};export{e as DEFAULT_BROWSER_ID,I as IDLE_CHECK_INTERVAL_MS,E as IDLE_TIMEOUT_MS,i as MAX_PAGES_PER_SESSION,S as MAX_SESSIONS,_ as MAX_SESSIONS_PER_AGENT,l as QUALITY_PRESETS,s as SESSION_TIMEOUT_MS,A as TILE_DIFF_CONFIG,p as WS_DEFAULT_HEARTBEAT_MS,T as WS_DEFAULT_IDLE_TIMEOUT_MS,a as WS_DEFAULT_MAX_SEND_BYTES,c as WS_HOST,x as WS_PORT,d as isQualityPreset,r as makeBrowserKey,n as parseBrowserKey};
|
|
2
2
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
|
|
2
2
|
import type { BrowserPool } from "./browser-pool.js";
|
|
3
|
-
import { type WsServerConfig } from "./types.js";
|
|
4
3
|
import type { PageListenerInfo } from "./page-listener-types.js";
|
|
4
|
+
import { type WsServerConfig } from "./types.js";
|
|
5
5
|
|
|
6
6
|
export declare class BrowserWsServer {
|
|
7
7
|
private readonly pool;
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{WebSocketServer as y,WebSocket as p}from"ws";import{DEFAULT_BROWSER_ID as f}from"./types.js";import{WS_DEFAULT_HEARTBEAT_MS as m,WS_DEFAULT_IDLE_TIMEOUT_MS as w,WS_DEFAULT_MAX_SEND_BYTES as k,WS_HOST as b,WS_PORT as v,isQualityPreset as _,QUALITY_PRESETS as I}from"./types.js";import{InputDispatcher as S}from"./input-dispatcher.js";import{installPickerOverlay as P,removePickerOverlay as C,pickElementAt as T,pickElementParent as J}from"./element-picker.js";const A="localhost";class E{constructor(e,r){this.wss=null,this.clients=new Set,this.mainHeartbeat=null,this.lastCursorQueryAt=0,this.lastCursor=null,this.pool=e,this.config={port:r?.port??v,host:r?.host??b,maxSendBytes:r?.maxSendBytes??k,idleTimeoutMs:r?.idleTimeoutMs??w,heartbeatMs:r?.heartbeatMs??m},this.inputDispatcher=new S(e),this.serverPort=this.config.port,this.pool.onSessionClose((t,s)=>{this.inputDispatcher.clearSession(t,s)})}start(){return this.wss?Promise.resolve({port:this.serverPort,host:this.config.host}):this.tryStart(this.config.port).catch(e=>{if(this.config.port===0)throw e;return console.warn(`[browser-ws] port ${this.config.port} bind failed (${e.message}); falling back to OS-assigned port`),this.tryStart(0)})}tryStart(e){return new Promise((r,t)=>{const s=new y({host:this.config.host,port:e,verifyClient:(o,i)=>{if(!o.origin){i(!0);return}/^https?:\/\/(localhost|127\.0\.0\.1)(:\d+)?$/.test(o.origin)?i(!0):i(!1,403,"origin not allowed")}});s.on("listening",()=>{const o=s.address(),i=typeof o=="object"&&o?o.port:e;this.serverPort=i,this.wss=s,this.attachHandlers(s),this.startMainHeartbeat(),r({port:i,host:this.config.host})}),s.on("error",o=>{this.wss||t(o)})})}async stop(){this.stopMainHeartbeat();for(const e of Array.from(this.clients))this.closeClient(e,1001,"server shutting down");this.wss&&(await new Promise(e=>{this.wss.close(()=>e())}),this.wss=null)}getPort(){return this.serverPort}getUrl(e,r){const t=this.config.host==="0.0.0.0"?A:this.config.host,s=r&&r.trim()?r.trim():f;return s===f?`ws://${t}:${this.serverPort}/agent/${encodeURIComponent(e)}`:`ws://${t}:${this.serverPort}/agent/${encodeURIComponent(e)}/${encodeURIComponent(s)}`}attachHandlers(e){e.on("connection",(r,t)=>{this.handleConnection(r,t).catch(()=>{try{r.close(1011,"internal error")}catch{}})})}async handleConnection(e,r){const t=new URL(r.url||"/","http://internal"),s=t.pathname.match(/^\/agent\/([^/]+)(?:\/([^/]+))?$/);if(!s){this.sendJson(e,{type:"error",message:`Invalid path; expected /agent/{agentId} or /agent/{agentId}/{browserId}, got ${t.pathname}`}),e.close(1008,"invalid path");return}const o=decodeURIComponent(s[1]),i=s[2]?decodeURIComponent(s[2]):f,n=this.pool.get(o,i);if(!n){this.sendJson(e,{type:"error",message:`Browser session not launched for ${o}/${i}. Call browser_launch first.`}),e.close(1008,"session not launched");return}const a={agentId:o,browserId:n.browserId,clientId:`${o}::${n.browserId}::${Date.now()}::${Math.random().toString(36).slice(2,8)}`,socket:e,heartbeatTimer:null,lastPongAt:Date.now(),unsubscribe:null},l=n.screencast.subscribe(u=>{this.sendFrame(e,u)});a.unsubscribe=l,e.on("pong",()=>{a.lastPongAt=Date.now()}),e.on("message",u=>{try{const d=u.toString("utf-8"),c=JSON.parse(d);if(c.type==="ping"){a.lastPongAt=Date.now(),this.sendJson(e,{type:"pong"});return}if(c.type==="input"&&typeof c.action=="string"){this.handleInputMessage(a,n,c.action,c);return}}catch{}}),e.on("close",()=>{this.closeClient(a,1e3,"client closed")}),e.on("error",()=>{this.closeClient(a,1011,"socket error")}),this.clients.add(a),this.sendJson(e,{type:"ready",agentId:o,browserId:n.browserId,viewport:{width:n.pages[n.activePageIndex]?.viewportSize()?.width??0,height:n.pages[n.activePageIndex]?.viewportSize()?.height??0},qualityPreset:n.qualityPreset})}async handleInputMessage(e,r,t,s){if(this.pool.touchActivity(e.agentId,e.browserId),t==="takeover"){const i=this.inputDispatcher.acquireTakeover(e.agentId,e.browserId,e.clientId);this.sendJson(e.socket,{type:"takeover_state",active:i?!0:r.humanTakeover,ok:i,error:i?void 0:"another client is taking over"});return}if(t==="release_takeover"){this.inputDispatcher.releaseTakeover(e.agentId,e.browserId,e.clientId),this.sendJson(e.socket,{type:"takeover_state",active:!1,ok:!0});return}if(t==="set_quality"){const i=s.preset;if(!_(i)){this.sendJson(e.socket,{type:"quality_changed",ok:!1,error:`invalid preset: ${String(s.preset)}`});return}const n=I[i];let a=!1;try{a=await r.screencast.updateQuality(n)}catch{a=!1}a&&(r.qualityPreset=i),this.sendJson(e.socket,{type:"quality_changed",preset:i,ok:a});return}if(t==="go_back"||t==="go_forward"||t==="reload"){if(!r.humanTakeover||r.takeoverClientId!==e.clientId){this.sendJson(e.socket,{type:"input_result",ok:!1,action:t,error:"not in takeover mode"});return}const i=r.pages[r.activePageIndex];if(!i||i.isClosed()){this.sendJson(e.socket,{type:"input_result",ok:!1,action:t,error:"active page is closed"});return}try{let n=!0,a="";try{a=i.url()}catch{}if(t==="go_back"?n=await i.goBack({waitUntil:"domcontentloaded",timeout:15e3}):t==="go_forward"?n=await i.goForward({waitUntil:"domcontentloaded",timeout:15e3}):await i.reload({waitUntil:"domcontentloaded",timeout:15e3}),n===null){let l="";try{l=i.url()}catch{}l&&l!==a&&(n=!0)}if(n===null&&t==="go_back"){const l=r.pages.indexOf(i);if(l>0){await this.pool.closePage(e.agentId,l,e.browserId);const u=r.pages[r.activePageIndex];u&&!u.isClosed()&&await r.screencast.forceCaptureAndPush(u).catch(()=>{}),this.sendJson(e.socket,{type:"input_result",ok:!0,action:t});return}}if(n===null){this.sendJson(e.socket,{type:"input_result",ok:!1,action:t,error:t==="go_back"?"no previous page in history":"no next page in history"});return}await new Promise(l=>setTimeout(l,200)),await r.screencast.restartAfterNavigation(i),this.sendJson(e.socket,{type:"input_result",ok:!0,action:t})}catch(n){this.sendJson(e.socket,{type:"input_result",ok:!1,action:t,error:n instanceof Error?n.message:String(n)})}return}if(t==="picker_start"||t==="picker_move"||t==="picker_stop"||t==="picker_parent"){if(!r.humanTakeover||r.takeoverClientId!==e.clientId){this.sendJson(e.socket,{type:"input_result",ok:!1,action:t,error:"not in takeover mode"});return}const i=r.pages[r.activePageIndex];if(!i||i.isClosed()){this.sendJson(e.socket,{type:"input_result",ok:!1,action:t,error:"active page is closed"});return}try{if(t==="picker_start")await P(i);else if(t==="picker_stop")await C(i);else if(t==="picker_parent"){const n=await J(i);this.sendJson(e.socket,{type:"picker_result",selector:n.selector,found:n.found,tag:n.tag,text:n.text})}else{const n=typeof s.x=="number"?s.x:0,a=typeof s.y=="number"?s.y:0,l=await T(i,n,a);this.sendJson(e.socket,{type:"picker_result",selector:l.selector,found:l.found,tag:l.tag,text:l.text})}this.sendJson(e.socket,{type:"input_result",ok:!0,action:t})}catch(n){this.sendJson(e.socket,{type:"input_result",ok:!1,action:t,error:n instanceof Error?n.message:String(n)})}return}const o=await this.inputDispatcher.dispatch(e.agentId,e.browserId,t,s);t==="mousemove"&&o.ok&&this.queryAndPushCursor(e,r,s),t==="press_key"&&o.ok&&typeof s.key=="string"&&(s.key==="Control+c"||s.key==="Meta+c")&&this.queryAndPushSelection(e,r),this.sendJson(e.socket,{type:"input_result",ok:o.ok,action:t,error:o.error})}async queryAndPushSelection(e,r){const t=r.pages[r.activePageIndex];if(!(!t||t.isClosed()))try{const s=await t.evaluate(()=>globalThis.getSelection?.()?.toString()??"");typeof s=="string"&&s.length>0&&this.sendJson(e.socket,{type:"selection_text",text:s})}catch{}}async queryAndPushCursor(e,r,t){const s=typeof t.x=="number"?t.x:void 0,o=typeof t.y=="number"?t.y:void 0;if(s===void 0||o===void 0)return;const i=Date.now();if(i-this.lastCursorQueryAt<60)return;this.lastCursorQueryAt=i;const n=r.pages[r.activePageIndex];if(!(!n||n.isClosed()))try{const a=await n.evaluate(l=>{const u=globalThis,d=u.document?.elementFromPoint(l.x,l.y);return d?u.window?.getComputedStyle(d)?.cursor??"default":"default"},{x:s,y:o});typeof a=="string"&&a!==this.lastCursor&&(this.lastCursor=a,this.sendJson(e.socket,{type:"cursor_changed",cursor:a}))}catch{}}sendFrame(e,r){if(e.readyState!==p.OPEN)return;const t=JSON.stringify({type:"frame",frame:r}),s=Buffer.byteLength(t,"utf-8");if(s>this.config.maxSendBytes){console.warn(`[browser-ws] frame payload ${s} bytes exceeds maxSendBytes ${this.config.maxSendBytes}; dropping`);return}try{e.send(t)}catch{}}sendJson(e,r){if(e.readyState===p.OPEN)try{e.send(JSON.stringify(r))}catch{}}closeClient(e,r,t){if(this.clients.has(e)){if(this.clients.delete(e),e.unsubscribe){try{e.unsubscribe()}catch{}e.unsubscribe=null}if(this.inputDispatcher.releaseTakeover(e.agentId,e.browserId,e.clientId),e.heartbeatTimer&&(clearInterval(e.heartbeatTimer),e.heartbeatTimer=null),e.socket.readyState===p.OPEN)try{e.socket.close(r,t)}catch{}}}startMainHeartbeat(){this.mainHeartbeat||(this.mainHeartbeat=setInterval(()=>{const e=Date.now();for(const r of Array.from(this.clients)){if(r.socket.readyState===p.OPEN)try{r.socket.ping()}catch{}e-r.lastPongAt>this.config.idleTimeoutMs&&this.closeClient(r,1001,"idle timeout")}},this.config.heartbeatMs),this.mainHeartbeat.unref&&this.mainHeartbeat.unref())}stopMainHeartbeat(){this.mainHeartbeat&&(clearInterval(this.mainHeartbeat),this.mainHeartbeat=null)}broadcastListenerUpdate(e,r,t){const s={type:"listener_update",agentId:e,browserId:r,listeners:t};for(const o of this.clients)o.agentId===e&&o.browserId===r&&this.sendJson(o.socket,s)}broadcastListenerTriggered(e,r,t){const s={type:"listener_triggered",agentId:e,browserId:r,...t};for(const o of this.clients)o.agentId===e&&o.browserId===r&&this.sendJson(o.socket,s)}}let h=null;async function B(g,e){return h||(h=new E(g,e),await h.start()),h}async function O(){h&&(await h.stop(),h=null)}export{E as BrowserWsServer,B as getBrowserWsServer,O as stopBrowserWsServer};
|
|
1
|
+
import{WebSocketServer as m,WebSocket as p}from"ws";import{installPickerOverlay as w,removePickerOverlay as b,pickElementAt as k,pickElementParent as v}from"./element-picker.js";import{InputDispatcher as _}from"./input-dispatcher.js";import{DEFAULT_BROWSER_ID as f}from"./types.js";import{WS_DEFAULT_HEARTBEAT_MS as S,WS_DEFAULT_IDLE_TIMEOUT_MS as I,WS_DEFAULT_MAX_SEND_BYTES as P,WS_HOST as C,WS_PORT as T,isQualityPreset as A,QUALITY_PRESETS as J}from"./types.js";const $="localhost";class E{constructor(e,t){this.wss=null,this.clients=new Set,this.mainHeartbeat=null,this.lastCursorQueryAt=0,this.lastCursor=null,this.pool=e,this.config={port:t?.port??T,host:t?.host??C,maxSendBytes:t?.maxSendBytes??P,idleTimeoutMs:t?.idleTimeoutMs??I,heartbeatMs:t?.heartbeatMs??S},this.inputDispatcher=new _(e),this.serverPort=this.config.port,this.pool.onSessionClose((r,i)=>{this.inputDispatcher.clearSession(r,i)})}start(){return this.wss?Promise.resolve({port:this.serverPort,host:this.config.host}):this.tryStart(this.config.port).catch(e=>{if(this.config.port===0)throw e;return console.warn(`[browser-ws] port ${this.config.port} bind failed (${e.message}); falling back to OS-assigned port`),this.tryStart(0)})}tryStart(e){return new Promise((t,r)=>{const i=new m({host:this.config.host,port:e,verifyClient:(o,n)=>{if(!o.origin){n(!0);return}/^https?:\/\/(localhost|127\.0\.0\.1)(:\d+)?$/.test(o.origin)?n(!0):n(!1,403,"origin not allowed")}});i.on("listening",()=>{const o=i.address(),n=typeof o=="object"&&o?o.port:e;this.serverPort=n,this.wss=i,this.attachHandlers(i),this.startMainHeartbeat(),t({port:n,host:this.config.host})}),i.on("error",o=>{this.wss||r(o)})})}async stop(){this.stopMainHeartbeat();for(const e of Array.from(this.clients))this.closeClient(e,1001,"server shutting down");this.wss&&(await new Promise(e=>{this.wss.close(()=>e())}),this.wss=null)}getPort(){return this.serverPort}getUrl(e,t){const r=this.config.host==="0.0.0.0"?$:this.config.host,i=t&&t.trim()?t.trim():f;return i===f?`ws://${r}:${this.serverPort}/agent/${encodeURIComponent(e)}`:`ws://${r}:${this.serverPort}/agent/${encodeURIComponent(e)}/${encodeURIComponent(i)}`}attachHandlers(e){e.on("connection",(t,r)=>{this.handleConnection(t,r).catch(()=>{try{t.close(1011,"internal error")}catch{}})})}async handleConnection(e,t){const r=new URL(t.url||"/","http://internal"),i=r.pathname.match(/^\/agent\/([^/]+)(?:\/([^/]+))?$/);if(!i){this.sendJson(e,{type:"error",message:`Invalid path; expected /agent/{agentId} or /agent/{agentId}/{browserId}, got ${r.pathname}`}),e.close(1008,"invalid path");return}const o=decodeURIComponent(i[1]),n=i[2]?decodeURIComponent(i[2]):f,s=this.pool.get(o,n);if(!s){this.sendJson(e,{type:"error",message:`Browser session not launched for ${o}/${n}. Call browser_launch first.`}),e.close(1008,"session not launched");return}const a={agentId:o,browserId:s.browserId,clientId:`${o}::${s.browserId}::${Date.now()}::${Math.random().toString(36).slice(2,8)}`,socket:e,heartbeatTimer:null,lastPongAt:Date.now(),unsubscribe:null},l=s.screencast.subscribe(h=>{this.sendFrame(e,h)});a.unsubscribe=l,e.on("pong",()=>{a.lastPongAt=Date.now()}),e.on("message",h=>{try{const y=h.toString("utf-8"),d=JSON.parse(y);if(d.type==="ping"){a.lastPongAt=Date.now(),this.sendJson(e,{type:"pong"});return}if(d.type==="input"&&typeof d.action=="string"){this.handleInputMessage(a,s,d.action,d);return}}catch{}}),e.on("close",()=>{this.closeClient(a,1e3,"client closed")}),e.on("error",()=>{this.closeClient(a,1011,"socket error")}),this.clients.add(a),this.sendJson(e,{type:"ready",agentId:o,browserId:s.browserId,viewport:{width:s.pages[s.activePageIndex]?.viewportSize()?.width??0,height:s.pages[s.activePageIndex]?.viewportSize()?.height??0},qualityPreset:s.qualityPreset,takeover:s.humanTakeover===!0});const c=s.pages[s.activePageIndex];c&&!c.isClosed()&&s.screencast.forceCaptureAndPush(c).catch(()=>{})}async handleInputMessage(e,t,r,i){if(this.pool.touchActivity(e.agentId,e.browserId),r==="takeover"){const n=this.inputDispatcher.acquireTakeover(e.agentId,e.browserId,e.clientId);this.sendJson(e.socket,{type:"takeover_state",active:n?!0:t.humanTakeover,ok:n,error:n?void 0:"another client is taking over"});return}if(r==="release_takeover"){this.inputDispatcher.releaseTakeover(e.agentId,e.browserId,e.clientId),this.sendJson(e.socket,{type:"takeover_state",active:!1,ok:!0});return}if(r==="set_quality"){const n=i.preset;if(!A(n)){this.sendJson(e.socket,{type:"quality_changed",ok:!1,error:`invalid preset: ${String(i.preset)}`});return}const s=J[n];let a=!1;try{a=await t.screencast.updateQuality(s)}catch{a=!1}a&&(t.qualityPreset=n),this.sendJson(e.socket,{type:"quality_changed",preset:n,ok:a});return}if(r==="resync"){const n=t.pages[t.activePageIndex];n&&!n.isClosed()&&await t.screencast.forceCaptureAndPush(n);return}if(r==="go_back"||r==="go_forward"||r==="reload"){if(!t.humanTakeover||t.takeoverClientId!==e.clientId){this.sendJson(e.socket,{type:"input_result",ok:!1,action:r,error:"not in takeover mode"});return}const n=t.pages[t.activePageIndex];if(!n||n.isClosed()){this.sendJson(e.socket,{type:"input_result",ok:!1,action:r,error:"active page is closed"});return}try{let s=!0,a="";try{a=n.url()}catch{}if(r==="go_back"?s=await n.goBack({waitUntil:"domcontentloaded",timeout:15e3}):r==="go_forward"?s=await n.goForward({waitUntil:"domcontentloaded",timeout:15e3}):await n.reload({waitUntil:"domcontentloaded",timeout:15e3}),s===null){let l="";try{l=n.url()}catch{}l&&l!==a&&(s=!0)}if(s===null&&r==="go_back"){const l=t.pages.indexOf(n);if(l>0){await this.pool.closePage(e.agentId,l,e.browserId);const c=t.pages[t.activePageIndex];c&&!c.isClosed()&&await t.screencast.forceCaptureAndPush(c).catch(()=>{}),this.sendJson(e.socket,{type:"input_result",ok:!0,action:r});return}}if(s===null){this.sendJson(e.socket,{type:"input_result",ok:!1,action:r,error:r==="go_back"?"no previous page in history":"no next page in history"});return}await new Promise(l=>setTimeout(l,200)),await t.screencast.restartAfterNavigation(n),this.sendJson(e.socket,{type:"input_result",ok:!0,action:r})}catch(s){this.sendJson(e.socket,{type:"input_result",ok:!1,action:r,error:s instanceof Error?s.message:String(s)})}return}if(r==="picker_start"||r==="picker_move"||r==="picker_stop"||r==="picker_parent"){if(!t.humanTakeover||t.takeoverClientId!==e.clientId){this.sendJson(e.socket,{type:"input_result",ok:!1,action:r,error:"not in takeover mode"});return}const n=t.pages[t.activePageIndex];if(!n||n.isClosed()){this.sendJson(e.socket,{type:"input_result",ok:!1,action:r,error:"active page is closed"});return}try{if(r==="picker_start")await w(n);else if(r==="picker_stop")await b(n);else if(r==="picker_parent"){const s=await v(n);this.sendJson(e.socket,{type:"picker_result",selector:s.selector,found:s.found,tag:s.tag,text:s.text})}else{const s=typeof i.x=="number"?i.x:0,a=typeof i.y=="number"?i.y:0,l=await k(n,s,a);this.sendJson(e.socket,{type:"picker_result",selector:l.selector,found:l.found,tag:l.tag,text:l.text})}this.sendJson(e.socket,{type:"input_result",ok:!0,action:r})}catch(s){this.sendJson(e.socket,{type:"input_result",ok:!1,action:r,error:s instanceof Error?s.message:String(s)})}return}const o=await this.inputDispatcher.dispatch(e.agentId,e.browserId,r,i);r==="mousemove"&&o.ok&&this.queryAndPushCursor(e,t,i),r==="press_key"&&o.ok&&typeof i.key=="string"&&(i.key==="Control+c"||i.key==="Meta+c")&&this.queryAndPushSelection(e,t),this.sendJson(e.socket,{type:"input_result",ok:o.ok,action:r,error:o.error})}async queryAndPushSelection(e,t){const r=t.pages[t.activePageIndex];if(!(!r||r.isClosed()))try{const i=await r.evaluate(()=>globalThis.getSelection?.()?.toString()??"");typeof i=="string"&&i.length>0&&this.sendJson(e.socket,{type:"selection_text",text:i})}catch{}}async queryAndPushCursor(e,t,r){const i=typeof r.x=="number"?r.x:void 0,o=typeof r.y=="number"?r.y:void 0;if(i===void 0||o===void 0)return;const n=Date.now();if(n-this.lastCursorQueryAt<60)return;this.lastCursorQueryAt=n;const s=t.pages[t.activePageIndex];if(!(!s||s.isClosed()))try{const a=await s.evaluate(l=>{const c=globalThis,h=c.document?.elementFromPoint(l.x,l.y);return h?c.window?.getComputedStyle(h)?.cursor??"default":"default"},{x:i,y:o});typeof a=="string"&&a!==this.lastCursor&&(this.lastCursor=a,this.sendJson(e.socket,{type:"cursor_changed",cursor:a}))}catch{}}sendFrame(e,t){if(e.readyState!==p.OPEN)return;if(t.binaryFrame){const o=t.binaryFrame.length;if(o>this.config.maxSendBytes){console.warn(`[browser-ws] binary frame ${o} bytes exceeds maxSendBytes ${this.config.maxSendBytes}; dropping`);return}try{e.send(t.binaryFrame)}catch{}return}const r=JSON.stringify({type:"frame",frame:t}),i=Buffer.byteLength(r,"utf-8");if(i>this.config.maxSendBytes){console.warn(`[browser-ws] frame payload ${i} bytes exceeds maxSendBytes ${this.config.maxSendBytes}; dropping`);return}try{e.send(r)}catch{}}sendJson(e,t){if(e.readyState===p.OPEN)try{e.send(JSON.stringify(t))}catch{}}closeClient(e,t,r){if(this.clients.has(e)){if(this.clients.delete(e),e.unsubscribe){try{e.unsubscribe()}catch{}e.unsubscribe=null}if(this.inputDispatcher.releaseTakeover(e.agentId,e.browserId,e.clientId),e.heartbeatTimer&&(clearInterval(e.heartbeatTimer),e.heartbeatTimer=null),e.socket.readyState===p.OPEN)try{e.socket.close(t,r)}catch{}}}startMainHeartbeat(){this.mainHeartbeat||(this.mainHeartbeat=setInterval(()=>{const e=Date.now();for(const t of Array.from(this.clients)){if(t.socket.readyState===p.OPEN)try{t.socket.ping()}catch{}e-t.lastPongAt>this.config.idleTimeoutMs&&this.closeClient(t,1001,"idle timeout")}},this.config.heartbeatMs),this.mainHeartbeat.unref&&this.mainHeartbeat.unref())}stopMainHeartbeat(){this.mainHeartbeat&&(clearInterval(this.mainHeartbeat),this.mainHeartbeat=null)}broadcastListenerUpdate(e,t,r){const i={type:"listener_update",agentId:e,browserId:t,listeners:r};for(const o of this.clients)o.agentId===e&&o.browserId===t&&this.sendJson(o.socket,i)}broadcastListenerTriggered(e,t,r){const i={type:"listener_triggered",agentId:e,browserId:t,...r};for(const o of this.clients)o.agentId===e&&o.browserId===t&&this.sendJson(o.socket,i)}}let u=null;async function O(g,e){return u||(u=new E(g,e),await u.start()),u}async function F(){u&&(await u.stop(),u=null)}export{E as BrowserWsServer,O as getBrowserWsServer,F as stopBrowserWsServer};
|
|
2
2
|
|
|
@@ -1,2 +1,8 @@
|
|
|
1
|
-
import{promises as
|
|
1
|
+
import{promises as y}from"node:fs";import p from"node:path";import{pathToFileURL as b}from"node:url";import{Router as C}from"express";import{getAcodeDataHome as j}from"../config.js";import{validateToken as m}from"../middleware/auth.js";import{createLogger as B}from"../utils/logger.js";import{getReleasedAcodeEntryPath as T}from"../services/acode-package.js";import{getAcodeBgManager as h}from"../services/acode-bg-manager.js";import{listPtySessions as E}from"./pty.js";let f=null;function I(){return f||(f=(async()=>{try{const e=T(),t=p.join(p.dirname(e),"background-command-store.js"),r=await import(b(t).href);return r.BackgroundCommandStore?new r.BackgroundCommandStore:(g.warn("[acode-bg-tasks] BackgroundCommandStore \u4E0D\u53EF\u7528,\u78C1\u76D8\u515C\u5E95\u7981\u7528"),null)}catch(e){return g.warn("[acode-bg-tasks] \u52A0\u8F7D BackgroundCommandStore \u5931\u8D25: %s",e?.message),null}})().then(e=>(e||(f=null),e)),f)}const g=B("acode-bg-tasks"),k=C();function _(e){return String(e).replace(/[^a-zA-Z0-9_-]/g,"_")}function L(e){return p.join(j(),"bg-tasks",_(e),"state.json")}k.get("/acode-bg-tasks/:agentId",m,async(e,t)=>{const r=e.params.agentId,i=Array.isArray(r)?r[0]:r;if(!i||!String(i).trim()){t.status(400).json({ok:!1,error:"agentId is required"});return}const d=L(String(i));try{const n=await y.readFile(d,"utf-8"),s=JSON.parse(n),c=(Array.isArray(s.tasks)?s.tasks:[]).map(o=>{const u=o;return{taskId:u.taskId,status:u.status,command:u.commandPreview??u.command,commandPreview:u.commandPreview,cwd:u.cwd,startedAt:u.startedAt,finishedAt:u.finishedAt,exitCode:u.exitCode,signal:u.signal,outputLineCount:u.outputLineCount,outputBytes:u.outputBytes}});t.json({ok:!0,agentId:String(i),updatedAt:typeof s.updatedAt=="number"?s.updatedAt:null,tasks:c})}catch(n){if((typeof n=="object"&&n!==null?Reflect.get(n,"code"):void 0)==="ENOENT"){t.json({ok:!0,agentId:String(i),updatedAt:null,tasks:[]});return}if(n instanceof SyntaxError){g.warn("[acode-bg-tasks] \u6587\u4EF6\u635F\u574F\uFF08JSON \u89E3\u6790\u5931\u8D25\uFF09: %s",d),t.json({ok:!0,agentId:String(i),updatedAt:null,tasks:[]});return}const a=n;g.error("[acode-bg-tasks] \u8BFB\u53D6\u5931\u8D25: %s",a.message),t.status(500).json({ok:!1,error:a.message})}}),k.get("/acode-agent-pty-sessions/:agentId",m,(e,t)=>{const r=e.params.agentId,i=Array.isArray(r)?r[0]:r;if(!i||!String(i).trim()){t.status(400).json({ok:!1,error:"agentId is required"});return}const d=String(i),s=E().filter(a=>a.ownerUserId===d);t.json({ok:!0,agentId:d,sessions:s})});const v=15e3;function S(e){return{taskId:e.taskId,status:e.status,exitCode:e.exitCode,signal:e.signal,startedAt:e.startedAt,finishedAt:e.finishedAt,command:e.command,lines:e.lines,nextSeq:e.nextSeq,totalLines:e.totalLines,totalBytes:e.totalBytes,truncated:e.truncated}}async function x(e,t){let i=null;try{i=await y.open(e,"r")}catch(d){if(d?.code==="ENOENT")return{text:[],fileExists:!1,truncated:!1};throw d}try{const n=(await i.stat()).size;if(n===0)return{text:[],fileExists:!0,truncated:!1};const s=Math.min(n,2097152),a=n-s,c=Buffer.alloc(s);await i.read(c,0,s,a);const u=c.toString("utf-8").split(`
|
|
2
|
+
`);return a>0&&u.shift(),{text:u.slice(-t),fileExists:!0,truncated:s<n}}finally{await i.close()}}k.get("/acode-bg-tasks/:agentId/:taskId/output",m,(e,t)=>{(async()=>{const r=String(e.params.agentId),i=String(e.params.taskId),d=Math.min(2e3,Math.max(100,Number(e.query.tailLines)||500));try{const n=await h();if(n){const s=n.getTaskOutputSnapshot(r,i);if(s){t.json({ok:!0,source:"memory",...S(s)});return}}}catch(n){g.warn("[acode-bg-tasks] \u5185\u5B58\u5FEB\u7167\u8BFB\u53D6\u5931\u8D25,\u964D\u7EA7\u8D70\u78C1\u76D8: %s",n?.message)}try{const n=await I();if(!n){t.status(503).json({ok:!1,error:"store unavailable"});return}const s=await n.readSession(r);if(!s){t.status(404).json({ok:!1,error:"task_not_found"});return}const a=s.tasks.find(l=>l.taskId===i);if(!a){t.status(404).json({ok:!1,error:"task_not_found"});return}const{text:c,fileExists:o,truncated:u}=await x(a.outputPath,d);t.json({ok:!0,source:"file",taskId:a.taskId,status:a.status,exitCode:a.exitCode,signal:a.signal,startedAt:a.startedAt,finishedAt:a.finishedAt,command:a.command,lines:c,nextSeq:0,totalLines:a.outputLineCount,totalBytes:a.outputBytes,truncated:u||!o})}catch(n){g.error("[acode-bg-tasks] \u78C1\u76D8\u515C\u5E95\u8BFB\u53D6\u5931\u8D25: %s",n?.message),t.status(500).json({ok:!1,error:"internal_error"})}})().catch(r=>{g.error("[acode-bg-tasks] output \u8BFB\u53D6\u5931\u8D25: %s",r?.message),t.status(500).json({ok:!1,error:"internal_error"})})}),k.get("/acode-bg-tasks/:agentId/:taskId/stream",m,(e,t)=>{(async()=>{const r=String(e.params.agentId),i=String(e.params.taskId);t.status(200),t.set({"Content-Type":"text/event-stream","Cache-Control":"no-cache, no-transform",Connection:"keep-alive","X-Accel-Buffering":"no"}),t.flushHeaders?.();const d=(a,c)=>{t.writableEnded||t.destroyed||(c!==void 0&&t.write(`id: ${c}
|
|
3
|
+
`),t.write(`data: ${JSON.stringify(a)}
|
|
4
|
+
|
|
5
|
+
`))};let n,s=null;try{n=await h(),n&&(s=n.getTaskOutputSnapshot(r,i))}catch(a){g.warn("[acode-bg-tasks] \u5185\u5B58\u8BFB\u53D6\u5931\u8D25,\u964D\u7EA7\u8D70\u78C1\u76D8: %s",a?.message)}if(s){if(d({type:"snapshot",source:"memory",...S(s)}),s.status!=="running"){d({type:"done",status:s.status,exitCode:s.exitCode,signal:s.signal}),t.end();return}let a=null,c=!1;const o=()=>{c||(c=!0,a?.(),clearInterval(u))},u=setInterval(()=>{if(t.writableEnded||t.destroyed){o();return}try{t.write(`: ping
|
|
6
|
+
|
|
7
|
+
`)}catch{o()}},v);if(u.unref?.(),a=n.subscribeTaskOutput(r,i,{onLine:l=>d({type:"output",stream:l.stream,text:l.text,seq:l.seq},l.seq),onDone:l=>{d({type:"done",status:l.status,exitCode:l.exitCode,signal:l.signal}),o(),t.end()}}),!a){d({type:"done",status:s.status,exitCode:s.exitCode,signal:s.signal}),o(),t.end();return}e.on("close",()=>{o(),t.writableEnded||t.end()}),e.on("aborted",()=>o());return}try{const a=await I();if(!a){d({type:"done",status:"killed",exitCode:null,signal:null}),t.end();return}const c=await a.readSession(r);if(!c){d({type:"done",status:"killed",exitCode:null,signal:null}),t.end();return}const o=c.tasks.find(w=>w.taskId===i);if(!o){d({type:"done",status:"killed",exitCode:null,signal:null}),t.end();return}const{text:u,fileExists:l,truncated:A}=await x(o.outputPath,500);d({type:"snapshot",source:"file",taskId:o.taskId,status:o.status,exitCode:o.exitCode,signal:o.signal,startedAt:o.startedAt,finishedAt:o.finishedAt,command:o.command,lines:u,nextSeq:0,totalLines:o.outputLineCount,totalBytes:o.outputBytes,truncated:A||!l}),d({type:"done",status:o.status,exitCode:o.exitCode,signal:o.signal}),t.end()}catch(a){g.error("[acode-bg-tasks] \u78C1\u76D8\u515C\u5E95 stream \u5931\u8D25: %s",a?.message),t.headersSent?t.end():t.status(500).json({ok:!1,error:"internal_error"})}})().catch(r=>{g.error("[acode-bg-tasks] stream \u5EFA\u7ACB\u5931\u8D25: %s",r?.message),t.headersSent?t.end():t.status(500).json({ok:!1,error:"internal_error"})})});export{k as backgroundTasksRouter};
|
|
2
8
|
|
|
@@ -34,8 +34,6 @@ export declare function buildSchedulerPingFailureResponse(error: Error, configur
|
|
|
34
34
|
|
|
35
35
|
export declare function buildAmbiguousSchedulerBaseUrlResponse(targetUrls: string[]): AmbiguousSchedulerBaseUrlResponse;
|
|
36
36
|
|
|
37
|
-
export declare function buildToolStatusDetectionTargets(enabledTools: string[]): string[];
|
|
38
|
-
|
|
39
37
|
export declare function buildUninstallFailureMessage(requestedTools: string[], toolStatus: Record<string, ToolInstallStatus>): string;
|
|
40
38
|
export declare function summarizeToolStatus(toolStatus: Record<string, ToolInstallStatus>): Record<string, Pick<ToolInstallStatus, "installed" | "version" | "executable" | "error">>;
|
|
41
39
|
export {};
|
package/dist/routes/instance.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{Router as H}from"express";import A from"axios";import{spawn as E}from"node:child_process";import{createHash as W,timingSafeEqual as G}from"node:crypto";import{fileURLToPath as V}from"node:url";import k from"node:path";import{adapterRegistry as J}from"../adapter/index.js";import{getBridgeVersion as R,resolveSchedulerBaseUrlFrom as Y}from"../config.js";import{validateToken as S,validateTokenOrPanelSession as v}from"../middleware/auth.js";import{getConfiguredConnectionKeys as _,getRegistrationState as Q,requestRuntimeAccessTokenRefreshForServer as Z,resolveSchedulerBaseUrlFromToken as ee}from"../services/auto-register.js";import{discoverCcSwitchConfiguredItems as te,loadCcSwitchSdk as re}from"../services/cc-switch-sdk.js";import{initInstance as se,syncLegacyStateFromSdk as oe}from"../services/instance-init-service.js";import{setAgentFeatures as ne,getAgentFeatures as x}from"../services/instance-features.js";import{createBridgeLifecycleOperation as L,deliverPendingBridgeLifecycleNotifications as C,loadLifecycleState as ae,removeBridgeLifecycleOperation as ie,upsertBridgeLifecycleOperation as b}from"../services/lifecycle-state.js";import{appendUpdateLog as p,readUpdateLog as ce}from"../services/update-log.js";import{checkBridgeUpdate as O}from"../utils/npm-version.js";import{loadInstanceState as $,saveInstanceState as P}from"../services/instance-state.js";import{getRuntimeAccessToken as le}from"../services/runtime-binding.js";import{detectToolStatuses as U,ensureToolsInstalled as de,SUPPORTED_INSTALLABLE_TOOLS as j,SUPPORTED_UNINSTALLABLE_TOOLS as q,uninstallTools as ue}from"../services/tool-installer.js";import{createLogger as ge}from"../utils/logger.js";const a=ge("instance"),fe=["acode","claude","opencode","codex"];let I=null;function We(r){I=r}const d=H();function F(r){return String(r||"").trim()||void 0}function D(r){const t=r.get("host");if(t)return`${r.protocol}://${t}`}function N(){const r=k.resolve(k.dirname(V(import.meta.url)),"..","..");return k.join(r,"dist","index.js")}function pe(){return new Promise((r,t)=>{const e=E(process.execPath,[N(),"update","--no-kill"],{shell:!1,windowsHide:!0});let s="",o="";e.stdout.on("data",n=>{s+=n.toString("utf8")}),e.stderr.on("data",n=>{o+=n.toString("utf8")}),e.on("error",t),e.on("close",n=>{r({exitCode:n,stdout:s,stderr:o})})})}function K(){const r=E(process.execPath,[N(),"service","restart"],{shell:!1,windowsHide:!0,stdio:["ignore","pipe","pipe"]});r.stdout.on("data",t=>{p(String(t)).catch(()=>{})}),r.stderr.on("data",t=>{p(String(t)).catch(()=>{})})}const M=3e3;async function me(r){const t={...r,status:"running",message:"Bridge \u66F4\u65B0\u6B63\u5728\u540E\u53F0\u6267\u884C\u3002"};await b(t);try{a.info("[update-bridge] \u6B63\u5728\u540E\u53F0\u6267\u884C awsb update --yes"),await p(`===== Bridge \u66F4\u65B0\u5F00\u59CB\uFF1Av${R()} -> latest\uFF08\u64CD\u4F5C ${r.operationId}\uFF09 =====`);const e=await pe();if(e.stdout&&await p(e.stdout),e.stderr&&await p(e.stderr),e.exitCode===0){await p("===== Bridge \u66F4\u65B0\u6210\u529F\uFF0C\u51C6\u5907\u81EA\u52A8\u91CD\u542F ===== ");const s={...t,status:"restarting",message:"Bridge \u66F4\u65B0\u5B8C\u6210\uFF0C\u6B63\u5728\u91CD\u542F..."};await b(s),a.info("[update-bridge] \u6B63\u5728\u6267\u884C awsb service restart"),K(),setTimeout(()=>{if(process.exitCode=42,I){I("BRIDGE_UPDATE_RESTART",!0).catch(o=>{const n=o;a.error("[update-bridge] graceful shutdown failed:",n),process.exit(42)});return}process.exit(42)},M)}else await p(`===== Bridge \u66F4\u65B0\u5931\u8D25\uFF1A\u9000\u51FA\u7801 ${e.exitCode??"unknown"} =====`),await b({...t,status:"failed",message:`Bridge \u66F4\u65B0\u5931\u8D25\uFF0Cnpm \u9000\u51FA\u7801: ${e.exitCode??"unknown"}`,error:e.stderr||e.stdout}),await C()}catch(e){const s=e;a.error("[update-bridge] background update failed:",s),await p(`===== Bridge \u66F4\u65B0\u5F02\u5E38\uFF1A${s.message} =====`).catch(()=>{}),await b({...t,status:"failed",message:"Bridge \u66F4\u65B0\u5931\u8D25\u3002",error:s.message||"update bridge failed"}),await C()}}async function z(r){const t=await import("node:fs/promises"),e=await import("node:path"),s=await import("node:os"),o=e.join(s.tmpdir(),"agentswork-runtime-bridge",".preserve-sessions");if(r){await t.mkdir(e.dirname(o),{recursive:!0}),await t.writeFile(o,JSON.stringify({preserveSessions:!0,timestamp:new Date().toISOString()}),"utf-8"),a.info(`[prepare-restart] \u5DF2\u521B\u5EFA\u4FDD\u7559\u4F1A\u8BDD\u6807\u8BB0\u6587\u4EF6: ${o}`);return}try{await t.unlink(o),a.info("[prepare-restart] \u5DF2\u5220\u9664\u4FDD\u7559\u4F1A\u8BDD\u6807\u8BB0\u6587\u4EF6")}catch{}}function he(r){return W("md5").update(r,"utf8").digest("hex")}function Se(r){const t=r.trim().toLowerCase();if(!/^[a-f0-9]{32}$/.test(t))return!1;const e=Buffer.from(t,"hex");return _().map(s=>s.trim()).filter(Boolean).some(s=>{const o=Buffer.from(he(s),"hex");return o.length===e.length&&G(o,e)})}function we(r){const e=_().length>0,s=R();return e?Se(r)?{status:200,body:{ok:!0,runtimeBridge:"healthy",version:s,connectionKeyMatched:!0,connectionKeyRequired:!0}}:{status:401,body:{ok:!1,error:"connection_key_mismatch",version:s,connectionKeyMatched:!1,connectionKeyRequired:!0}}:{status:200,body:{ok:!0,runtimeBridge:"healthy",version:s,connectionKeyMatched:!0,connectionKeyRequired:!1}}}function be(r,t){const s=t.trim()||"http://localhost:8080",o=/^https?:\/\/(localhost|127\.0\.0\.1)(:\d+)?(?:\/|$)/i.test(s);return{ok:!1,error:r.message||"scheduler ping failed",failureStage:"scheduler_ping",runtimeBridge:"healthy",schedulerBaseUrl:s,hint:o?"aws-runtime-bridge \u5DF2\u8FDE\u901A\uFF0C\u4F46\u5B83\u56DE\u8FDE\u8C03\u5EA6\u4E2D\u5FC3\u5931\u8D25\u3002\u8BF7\u5728 bridge \u542F\u52A8\u73AF\u5883\u4E2D\u5C06 AWS_RUNTIME_SCHEDULER_BASE_URL \u914D\u7F6E\u4E3A\u8BE5\u5B9E\u4F8B\u53EF\u8BBF\u95EE\u7684\u8C03\u5EA6\u4E2D\u5FC3\u5730\u5740\uFF0C\u4F8B\u5982 http://<server-host>:7380\u3002":"aws-runtime-bridge \u5DF2\u8FDE\u901A\uFF0C\u4F46\u5B83\u56DE\u8FDE\u8C03\u5EA6\u4E2D\u5FC3\u5931\u8D25\u3002\u8BF7\u786E\u8BA4 AWS_RUNTIME_SCHEDULER_BASE_URL \u6307\u5411\u7684\u8C03\u5EA6\u4E2D\u5FC3\u5730\u5740\u53EF\u4ECE bridge \u673A\u5668\u8BBF\u95EE\uFF0C\u5E76\u4E14\u8FD0\u884C\u65F6\u8BBF\u95EE\u4EE4\u724C\u4ECD\u6709\u6548\u3002"}}function ye(r){if(typeof r!="object"||r===null)return;const t=Reflect.get(r,"response");if(typeof t!="object"||t===null)return;const e=Reflect.get(t,"status");return typeof e=="number"?e:void 0}async function Ie(r,t){try{const e=await A.get(`${r}/api/runtime/ping`,{headers:{"X-Runtime-Token":t}});return{schedulerReachable:e.status>=200&&e.status<300,tokenRefreshed:!1}}catch(e){if(ye(e)!==401)throw e;const s=await Z(r);if(!s.success||!s.runtimeAccessToken)throw e;const o=await A.get(`${r}/api/runtime/ping`,{headers:{"X-Runtime-Token":s.runtimeAccessToken}});return{schedulerReachable:o.status>=200&&o.status<300,tokenRefreshed:!0}}}function Be(r){return{ok:!1,error:"ambiguous_scheduler_base_url",failureStage:"scheduler_ping",runtimeBridge:"healthy",schedulerBaseUrl:"",autoRegisterTargetUrls:r,hint:"Multiple autoRegisterTargets.serverUrl values were found, so bridge cannot safely infer which scheduler /runtime/ping should call back. Set AWS_RUNTIME_SCHEDULER_BASE_URL explicitly, or use future schedulerId/token-bound multi-scheduler routing."}}function Te(r){return Array.from(new Set([...r,...fe].map(t=>String(t||"").trim().toLowerCase()).filter(Boolean)))}function ke(r,t){const e=r.filter(s=>t[s]?.installed);return e.length===0?"":e.map(s=>{const o=t[s],n=o?.error||o?.executable||"command is still available";return`${s}: ${n}`}).join("; ")}function B(r){return Object.fromEntries(Object.entries(r||{}).map(([t,e])=>[t,{installed:!!e?.installed,version:e?.version||null,executable:e?.executable||null,error:e?.error||null}]))}d.get("/check-update",async(r,t)=>{t.json(await O())}),d.get("/update-log",v,async(r,t)=>{try{const e=await ce();t.json({ok:!0,...e})}catch(e){const s=e;a.error("[update-log] Error:",s),t.status(500).json({ok:!1,error:s.message||"read update log failed"})}}),d.get("/healthz",(r,t)=>{t.json({ok:!0,runtimeBridge:"healthy"})}),d.get("/connection-check",(r,t)=>{const e=String(r.header("X-Bridge-Connection-Key-MD5")||""),s=we(e);t.status(s.status).json(s.body)}),d.get("/ping",S,async(r,t)=>{let e=Y(r.header("X-Scheduler-Base-Url"),r.header("X-Bridge-Instance-Id"),r.header("X-Bridge-Instance-Name"));if(e.source==="ambiguous-auto-register-targets"){const o=ee(r.header("X-Runtime-Token"));if(o)e={url:o,source:"token-match",ambiguousTargetUrls:[]};else{const n=r.header("X-Bridge-Instance-Id");if(n){const i=Q(),c=Object.entries(i.registrations).find(([,l])=>l===n)?.[0];c&&(e={url:c,source:"instance-id-match",ambiguousTargetUrls:[]})}if(!e.url||e.source==="ambiguous-auto-register-targets"){t.status(400).json(Be(e.ambiguousTargetUrls));return}}}const s=e.url;try{const o=le(s);if(!o){t.status(401).json({ok:!1,error:"runtime_access_token_required"});return}const n=await Ie(s,o);t.json({ok:!0,runtimeBridge:"healthy",version:R(),schedulerReachable:n.schedulerReachable,tokenRefreshed:n.tokenRefreshed,schedulerBaseUrl:s,instanceId:e.instanceId,instanceName:e.instanceName})}catch(o){const n=o;t.status(502).json(be(n,s))}}),d.post("/init-instance",S,async(r,t)=>{const{agentId:e,workspacePath:s,skillEnabled:o,mcpEnabled:n,ccSwitchEnabledTools:i,skillPackage:c,skillPackages:l,mcpServers:m}=r.body||{};if(!e||!s){t.status(400).json({error:"agentId and workspacePath are required"});return}try{a.info("[init-instance] Request received",{agentId:String(e),workspacePath:String(s),skillEnabled:o,mcpEnabled:n,ccSwitchEnabledTools:i});const u=await se(e,s,{skillEnabled:o,mcpEnabled:n,ccSwitchEnabledTools:i,skillPackage:c,skillPackages:l,mcpServers:m});a.info("[init-instance] Initialization completed",{agentId:String(e),ok:u.ok,logCount:u.logs?.length||0,enabledTools:u.enabledTools}),t.json(u)}catch(u){const g=u;a.error("[init-instance] Initialization failed",{agentId:String(e),workspacePath:String(s),error:g?.message||String(u),stack:g?.stack||null}),t.status(400).json({error:g?.message||"initialize instance failed"})}}),d.post("/features",S,async(r,t)=>{const{agentId:e,features:s}=r.body||{};if(!e){t.status(400).json({error:"agentId is required"});return}ne(String(e),s),a.info("[instance/features] \u80FD\u529B\u5F00\u5173\u5DF2\u66F4\u65B0",{agentId:String(e),features:x(String(e))});let o=0;for(const n of J.getAll())if(typeof n.applyAgentFeatures=="function")try{await n.applyAgentFeatures(String(e)),o+=1}catch(i){a.error("[instance/features] adapter \u70ED\u540C\u6B65\u5931\u8D25",{agentId:String(e),providerId:n.providerId,error:i?.message||String(i)})}t.json({ok:!0,agentId:String(e),features:x(String(e)),hotSyncedAdapters:o})}),d.post("/cc-switch/state",S,async(r,t)=>{const{agentId:e}=r.body||{};if(!e){t.status(400).json({error:"agentId is required"});return}try{a.info("[cc-switch/state] Request received",{agentId:String(e)});const s=await re(e),o=await te(s),i=(await oe(e,s)).state,c=Te(i.enabledTools||[]);a.info("[cc-switch/state] Detecting SDK statuses",{agentId:String(e),detectionTargets:c});const l=await U(c);a.info("[cc-switch/state] SDK status detection completed",{agentId:String(e),toolStatus:B(l),importedMcpCount:o.mcpServers,importedSkillCount:o.skills}),t.json({ok:!0,agentId:String(e),imported:{mcpServers:o.mcpServers,skills:o.skills},state:{...i,toolStatus:l}})}catch(s){const o=s;a.error("[cc-switch/state] Failed to load state",{agentId:String(e),error:o?.message||String(s),stack:o?.stack||null}),t.status(400).json({error:o?.message||"load state failed"})}}),d.post("/cc-switch/install-tools",S,async(r,t)=>{const{agentId:e,tools:s}=r.body||{};if(!e){t.status(400).json({error:"agentId is required"});return}const o=Array.isArray(s)?s.map(c=>String(c||"").trim().toLowerCase()).filter(Boolean):[];a.info("[cc-switch/install-tools] Request received",{agentId:String(e),requestedTools:o});const n=new Set(j),i=o.filter(c=>n.has(c));if(a.info("[cc-switch/install-tools] Request normalized",{agentId:String(e),requestedTools:o,installableTools:i,supportedInstallableTools:j}),i.length===0){a.warn("[cc-switch/install-tools] No supported installable tools requested",{agentId:String(e),requestedTools:o}),t.status(400).json({error:`tools must include ${j.join(", ")}`});return}try{const c=await $(e),l=Array.from(new Set([...c.enabledTools||[],...i]));a.info("[cc-switch/install-tools] Starting installer",{agentId:String(e),installableTools:i,previousEnabledTools:c.enabledTools||[],nextEnabledTools:l});const m=await de(i);a.info("[cc-switch/install-tools] Installer completed",{agentId:String(e),toolStatus:B(m)});const u=await U(l);a.info("[cc-switch/install-tools] Refreshed enabled SDK statuses",{agentId:String(e),refreshedToolStatus:B(u)});const g={...u,...m},h=await P(e,{...c,enabledTools:l,toolStatus:g}),y=new Set(i),w=Object.entries(g).filter(([f])=>y.has(f)).filter(([,f])=>!f.installed).map(([f,T])=>({tool:f,error:T.error||null}));w.length>0&&a.warn("[cc-switch/install-tools] Some SDKs remain unavailable",{agentId:String(e),failedTools:w}),a.info("[cc-switch/install-tools] State saved",{agentId:String(e),enabledTools:h.enabledTools,toolStatus:B(g)}),t.json({ok:w.length===0,agentId:String(e),toolStatus:g,failedTools:w,state:h})}catch(c){const l=c;a.error("[cc-switch/install-tools] Install route failed",{agentId:String(e),requestedTools:o,installableTools:i,error:l?.message||String(c),stack:l?.stack||null}),t.status(400).json({error:l?.message||"install tools failed"})}}),d.post("/cc-switch/uninstall-tools",S,async(r,t)=>{const{agentId:e,tools:s}=r.body||{};if(!e){t.status(400).json({error:"agentId is required"});return}const o=Array.isArray(s)?s.map(c=>String(c||"").trim().toLowerCase()).filter(Boolean):[],n=new Set(q),i=o.filter(c=>n.has(c));if(i.length===0){t.status(400).json({error:`tools must include ${q.join(", ")}`});return}try{const c=await $(e),l=new Set(i),m=(c.enabledTools||[]).filter(T=>!l.has(String(T||"").trim().toLowerCase())),u=await ue(i),h={...await U([...m,...i]),...u},y=ke(i,h),w=y?c.enabledTools||[]:m,f=await P(e,{...c,enabledTools:w,toolStatus:h});if(y){t.status(400).json({ok:!1,agentId:String(e),error:`uninstall incomplete: ${y}`,toolStatus:h,state:f});return}t.json({ok:!0,agentId:String(e),toolStatus:h,state:f})}catch(c){const l=c;t.status(400).json({error:l?.message||"uninstall tools failed"})}}),d.post("/prepare-restart",S,async(r,t)=>{const{preserveSessions:e=!0}=r.body||{};try{a.info(`[prepare-restart] \u6536\u5230\u51C6\u5907\u91CD\u542F\u901A\u77E5\uFF0CpreserveSessions=${e}`),await z(!!e),t.json({ok:!0,preserveSessions:e,message:e?"Bridge will preserve sessions for aws-mcp-server restart":"Bridge will clean up sessions on shutdown"})}catch(s){const o=s;a.error("[prepare-restart] Error:",o),t.status(500).json({error:o.message})}});const Re={update:15*60*1e3,restart:5*60*1e3};async function ve(r){try{const t=await ae(),e=Date.now(),s=Re[r],o=[];for(const n of t.operations){if(n.operationType!==r||!["accepted","running","restarting"].includes(n.status))continue;const i=new Date(n.updatedAt).getTime();if(!Number.isNaN(i)&&e-i<=s)return{operationId:n.operationId};o.push(n.operationId)}for(const n of o)try{await ie(n),a.info(`[${r}-bridge] \u5DF2\u6E05\u7406\u8D85\u65F6\u672A\u4EA4\u4ED8\u7684\u9648\u65E7\u64CD\u4F5C: ${n}`)}catch(i){const c=i;a.warn(`[${r}-bridge] \u6E05\u7406\u9648\u65E7\u64CD\u4F5C\u5931\u8D25\uFF0C\u7EE7\u7EED\u653E\u884C: ${n}, ${c.message}`)}return null}catch(t){const e=t;return a.warn(`[${r}-bridge] \u5728\u9014\u64CD\u4F5C\u68C0\u67E5\u5931\u8D25\uFF0C\u653E\u884C\u8BF7\u6C42: ${e.message}`),null}}async function X(r,t,e){const s=await ve(r);return s?(e.status(409).json({error:t,operationId:s.operationId}),!0):!1}d.post("/update-bridge",v,async(r,t)=>{const e=F(r.body?.requestedByUserId);try{if(!e){t.status(400).json({error:"requestedByUserId is required"});return}if(await X("update","\u5B9E\u4F8B Bridge \u66F4\u65B0\u6B63\u5728\u6267\u884C\u4E2D\uFF0C\u8BF7\u52FF\u91CD\u590D\u89E6\u53D1",t))return;const s=await O(),o=L({operationType:"update",requestedByUserId:e,schedulerBaseUrl:r.body?.schedulerBaseUrl,runtimeBridgeBaseUrl:D(r),status:"accepted",message:"Bridge \u66F4\u65B0\u8BF7\u6C42\u5DF2\u6536\u5230\uFF0C\u5C06\u5728\u540E\u53F0\u6267\u884C\u5E76\u81EA\u52A8\u91CD\u542F\u3002",latestVersion:s.ok&&s.latestVersion||void 0});await b(o),me(o),t.json({ok:!0,operationId:o.operationId,message:"Bridge \u66F4\u65B0\u5DF2\u5728\u540E\u53F0\u5F00\u59CB\uFF0C\u66F4\u65B0\u5B8C\u6210\u540E\u5C06\u81EA\u52A8\u91CD\u542F\u5B9E\u4F8B Bridge\u3002"})}catch(s){const o=s;a.error("[update-bridge] Error:",o),t.status(500).json({error:o.message||"update bridge failed"})}}),d.post("/restart-bridge",v,async(r,t)=>{const{preserveSessions:e=!0}=r.body||{},s=F(r.body?.requestedByUserId);try{if(!s){t.status(400).json({error:"requestedByUserId is required"});return}if(await X("restart","\u5B9E\u4F8B Bridge \u6B63\u5728\u91CD\u542F\u4E2D\uFF0C\u8BF7\u7A0D\u5019\u518D\u8BD5",t))return;a.info(`[restart-bridge] \u6536\u5230\u5B9E\u4F8B Bridge \u91CD\u542F\u8BF7\u6C42\uFF0CpreserveSessions=${e}`);const o=L({operationType:"restart",requestedByUserId:s,schedulerBaseUrl:r.body?.schedulerBaseUrl,runtimeBridgeBaseUrl:D(r),status:"accepted",message:"Bridge \u5DF2\u6536\u5230\u91CD\u542F\u8BF7\u6C42\uFF1B\u4E0B\u6B21\u542F\u52A8\u540E\u4F1A\u53D1\u9001\u786E\u8BA4\u901A\u77E5\u3002"});await b(o),await z(!!e),t.json({ok:!0,operationId:o.operationId,preserveSessions:!!e,message:"Bridge \u6B63\u5728\u91CD\u542F\uFF0C\u670D\u52A1\u7BA1\u7406\u5668\u4F1A\u81EA\u52A8\u62C9\u8D77\u5B9E\u4F8B\uFF1B\u4E0B\u6B21\u542F\u52A8\u540E\u4F1A\u901A\u8FC7\u6D88\u606F\u4E2D\u5FC3\u901A\u77E5\u7ED3\u679C\u3002"}),a.info("[restart-bridge] \u6B63\u5728\u6267\u884C awsb service restart"),K(),setTimeout(()=>{if(process.exitCode=42,I){I("PANEL_RESTART",!!e).catch(n=>{const i=n;a.error("[restart-bridge] graceful shutdown failed:",i),process.exit(42)});return}process.exit(42)},M)}catch(o){const n=o;a.error("[restart-bridge] Error:",n),t.status(500).json({error:n.message||"restart bridge failed"})}});export{Be as buildAmbiguousSchedulerBaseUrlResponse,we as buildConnectionCheckResponse,be as buildSchedulerPingFailureResponse,Te as buildToolStatusDetectionTargets,ke as buildUninstallFailureMessage,d as instanceRouter,We as setGracefulShutdownFn,B as summarizeToolStatus};
|
|
1
|
+
import{Router as H}from"express";import E from"axios";import{spawn as A}from"node:child_process";import{createHash as W,timingSafeEqual as G}from"node:crypto";import{fileURLToPath as V}from"node:url";import T from"node:path";import{adapterRegistry as J}from"../adapter/index.js";import{getBridgeVersion as R,resolveSchedulerBaseUrlFrom as Q}from"../config.js";import{validateToken as S,validateTokenOrPanelSession as v}from"../middleware/auth.js";import{getConfiguredConnectionKeys as _,getRegistrationState as Y,requestRuntimeAccessTokenRefreshForServer as Z,resolveSchedulerBaseUrlFromToken as ee}from"../services/auto-register.js";import{discoverCcSwitchConfiguredItems as te,loadCcSwitchSdk as re}from"../services/cc-switch-sdk.js";import{initInstance as se,syncLegacyStateFromSdk as oe}from"../services/instance-init-service.js";import{setAgentFeatures as ne,getAgentFeatures as x}from"../services/instance-features.js";import{createBridgeLifecycleOperation as C,deliverPendingBridgeLifecycleNotifications as L,loadLifecycleState as ae,removeBridgeLifecycleOperation as ie,upsertBridgeLifecycleOperation as b}from"../services/lifecycle-state.js";import{appendUpdateLog as p,readUpdateLog as ce}from"../services/update-log.js";import{checkBridgeUpdate as O}from"../utils/npm-version.js";import{loadInstanceState as $,saveInstanceState as q}from"../services/instance-state.js";import{getRuntimeAccessToken as le}from"../services/runtime-binding.js";import{detectToolStatuses as U,ensureToolsInstalled as de,SUPPORTED_INSTALLABLE_TOOLS as j,SUPPORTED_UNINSTALLABLE_TOOLS as F,uninstallTools as ue}from"../services/tool-installer.js";import{createLogger as ge}from"../utils/logger.js";const a=ge("instance");import{buildToolStatusDetectionTargets as fe}from"./instance-tool-status.js";let I=null;function We(r){I=r}const d=H();function P(r){return String(r||"").trim()||void 0}function D(r){const t=r.get("host");if(t)return`${r.protocol}://${t}`}function N(){const r=T.resolve(T.dirname(V(import.meta.url)),"..","..");return T.join(r,"dist","index.js")}function pe(){return new Promise((r,t)=>{const e=A(process.execPath,[N(),"update","--no-kill"],{shell:!1,windowsHide:!0});let s="",o="";e.stdout.on("data",n=>{s+=n.toString("utf8")}),e.stderr.on("data",n=>{o+=n.toString("utf8")}),e.on("error",t),e.on("close",n=>{r({exitCode:n,stdout:s,stderr:o})})})}function K(){const r=A(process.execPath,[N(),"service","restart"],{shell:!1,windowsHide:!0,stdio:["ignore","pipe","pipe"]});r.stdout.on("data",t=>{p(String(t)).catch(()=>{})}),r.stderr.on("data",t=>{p(String(t)).catch(()=>{})})}const M=3e3;async function me(r){const t={...r,status:"running",message:"Bridge \u66F4\u65B0\u6B63\u5728\u540E\u53F0\u6267\u884C\u3002"};await b(t);try{a.info("[update-bridge] \u6B63\u5728\u540E\u53F0\u6267\u884C awsb update --yes"),await p(`===== Bridge \u66F4\u65B0\u5F00\u59CB\uFF1Av${R()} -> latest\uFF08\u64CD\u4F5C ${r.operationId}\uFF09 =====`);const e=await pe();if(e.stdout&&await p(e.stdout),e.stderr&&await p(e.stderr),e.exitCode===0){await p("===== Bridge \u66F4\u65B0\u6210\u529F\uFF0C\u51C6\u5907\u81EA\u52A8\u91CD\u542F ===== ");const s={...t,status:"restarting",message:"Bridge \u66F4\u65B0\u5B8C\u6210\uFF0C\u6B63\u5728\u91CD\u542F..."};await b(s),a.info("[update-bridge] \u6B63\u5728\u6267\u884C awsb service restart"),K(),setTimeout(()=>{if(process.exitCode=42,I){I("BRIDGE_UPDATE_RESTART",!0).catch(o=>{const n=o;a.error("[update-bridge] graceful shutdown failed:",n),process.exit(42)});return}process.exit(42)},M)}else await p(`===== Bridge \u66F4\u65B0\u5931\u8D25\uFF1A\u9000\u51FA\u7801 ${e.exitCode??"unknown"} =====`),await b({...t,status:"failed",message:`Bridge \u66F4\u65B0\u5931\u8D25\uFF0Cnpm \u9000\u51FA\u7801: ${e.exitCode??"unknown"}`,error:e.stderr||e.stdout}),await L()}catch(e){const s=e;a.error("[update-bridge] background update failed:",s),await p(`===== Bridge \u66F4\u65B0\u5F02\u5E38\uFF1A${s.message} =====`).catch(()=>{}),await b({...t,status:"failed",message:"Bridge \u66F4\u65B0\u5931\u8D25\u3002",error:s.message||"update bridge failed"}),await L()}}async function z(r){const t=await import("node:fs/promises"),e=await import("node:path"),s=await import("node:os"),o=e.join(s.tmpdir(),"agentswork-runtime-bridge",".preserve-sessions");if(r){await t.mkdir(e.dirname(o),{recursive:!0}),await t.writeFile(o,JSON.stringify({preserveSessions:!0,timestamp:new Date().toISOString()}),"utf-8"),a.info(`[prepare-restart] \u5DF2\u521B\u5EFA\u4FDD\u7559\u4F1A\u8BDD\u6807\u8BB0\u6587\u4EF6: ${o}`);return}try{await t.unlink(o),a.info("[prepare-restart] \u5DF2\u5220\u9664\u4FDD\u7559\u4F1A\u8BDD\u6807\u8BB0\u6587\u4EF6")}catch{}}function he(r){return W("md5").update(r,"utf8").digest("hex")}function Se(r){const t=r.trim().toLowerCase();if(!/^[a-f0-9]{32}$/.test(t))return!1;const e=Buffer.from(t,"hex");return _().map(s=>s.trim()).filter(Boolean).some(s=>{const o=Buffer.from(he(s),"hex");return o.length===e.length&&G(o,e)})}function we(r){const e=_().length>0,s=R();return e?Se(r)?{status:200,body:{ok:!0,runtimeBridge:"healthy",version:s,connectionKeyMatched:!0,connectionKeyRequired:!0}}:{status:401,body:{ok:!1,error:"connection_key_mismatch",version:s,connectionKeyMatched:!1,connectionKeyRequired:!0}}:{status:200,body:{ok:!0,runtimeBridge:"healthy",version:s,connectionKeyMatched:!0,connectionKeyRequired:!1}}}function be(r,t){const s=t.trim()||"http://localhost:8080",o=/^https?:\/\/(localhost|127\.0\.0\.1)(:\d+)?(?:\/|$)/i.test(s);return{ok:!1,error:r.message||"scheduler ping failed",failureStage:"scheduler_ping",runtimeBridge:"healthy",schedulerBaseUrl:s,hint:o?"aws-runtime-bridge \u5DF2\u8FDE\u901A\uFF0C\u4F46\u5B83\u56DE\u8FDE\u8C03\u5EA6\u4E2D\u5FC3\u5931\u8D25\u3002\u8BF7\u5728 bridge \u542F\u52A8\u73AF\u5883\u4E2D\u5C06 AWS_RUNTIME_SCHEDULER_BASE_URL \u914D\u7F6E\u4E3A\u8BE5\u5B9E\u4F8B\u53EF\u8BBF\u95EE\u7684\u8C03\u5EA6\u4E2D\u5FC3\u5730\u5740\uFF0C\u4F8B\u5982 http://<server-host>:7380\u3002":"aws-runtime-bridge \u5DF2\u8FDE\u901A\uFF0C\u4F46\u5B83\u56DE\u8FDE\u8C03\u5EA6\u4E2D\u5FC3\u5931\u8D25\u3002\u8BF7\u786E\u8BA4 AWS_RUNTIME_SCHEDULER_BASE_URL \u6307\u5411\u7684\u8C03\u5EA6\u4E2D\u5FC3\u5730\u5740\u53EF\u4ECE bridge \u673A\u5668\u8BBF\u95EE\uFF0C\u5E76\u4E14\u8FD0\u884C\u65F6\u8BBF\u95EE\u4EE4\u724C\u4ECD\u6709\u6548\u3002"}}function ye(r){if(typeof r!="object"||r===null)return;const t=Reflect.get(r,"response");if(typeof t!="object"||t===null)return;const e=Reflect.get(t,"status");return typeof e=="number"?e:void 0}async function Ie(r,t){try{const e=await E.get(`${r}/api/runtime/ping`,{headers:{"X-Runtime-Token":t}});return{schedulerReachable:e.status>=200&&e.status<300,tokenRefreshed:!1}}catch(e){if(ye(e)!==401)throw e;const s=await Z(r);if(!s.success||!s.runtimeAccessToken)throw e;const o=await E.get(`${r}/api/runtime/ping`,{headers:{"X-Runtime-Token":s.runtimeAccessToken}});return{schedulerReachable:o.status>=200&&o.status<300,tokenRefreshed:!0}}}function Be(r){return{ok:!1,error:"ambiguous_scheduler_base_url",failureStage:"scheduler_ping",runtimeBridge:"healthy",schedulerBaseUrl:"",autoRegisterTargetUrls:r,hint:"Multiple autoRegisterTargets.serverUrl values were found, so bridge cannot safely infer which scheduler /runtime/ping should call back. Set AWS_RUNTIME_SCHEDULER_BASE_URL explicitly, or use future schedulerId/token-bound multi-scheduler routing."}}function ke(r,t){const e=r.filter(s=>t[s]?.installed);return e.length===0?"":e.map(s=>{const o=t[s],n=o?.error||o?.executable||"command is still available";return`${s}: ${n}`}).join("; ")}function B(r){return Object.fromEntries(Object.entries(r||{}).map(([t,e])=>[t,{installed:!!e?.installed,version:e?.version||null,executable:e?.executable||null,error:e?.error||null}]))}d.get("/check-update",async(r,t)=>{t.json(await O())}),d.get("/update-log",v,async(r,t)=>{try{const e=await ce();t.json({ok:!0,...e})}catch(e){const s=e;a.error("[update-log] Error:",s),t.status(500).json({ok:!1,error:s.message||"read update log failed"})}}),d.get("/healthz",(r,t)=>{t.json({ok:!0,runtimeBridge:"healthy"})}),d.get("/connection-check",(r,t)=>{const e=String(r.header("X-Bridge-Connection-Key-MD5")||""),s=we(e);t.status(s.status).json(s.body)}),d.get("/ping",S,async(r,t)=>{let e=Q(r.header("X-Scheduler-Base-Url"),r.header("X-Bridge-Instance-Id"),r.header("X-Bridge-Instance-Name"));if(e.source==="ambiguous-auto-register-targets"){const o=ee(r.header("X-Runtime-Token"));if(o)e={url:o,source:"token-match",ambiguousTargetUrls:[]};else{const n=r.header("X-Bridge-Instance-Id");if(n){const i=Y(),c=Object.entries(i.registrations).find(([,l])=>l===n)?.[0];c&&(e={url:c,source:"instance-id-match",ambiguousTargetUrls:[]})}if(!e.url||e.source==="ambiguous-auto-register-targets"){t.status(400).json(Be(e.ambiguousTargetUrls));return}}}const s=e.url;try{const o=le(s);if(!o){t.status(401).json({ok:!1,error:"runtime_access_token_required"});return}const n=await Ie(s,o);t.json({ok:!0,runtimeBridge:"healthy",version:R(),schedulerReachable:n.schedulerReachable,tokenRefreshed:n.tokenRefreshed,schedulerBaseUrl:s,instanceId:e.instanceId,instanceName:e.instanceName})}catch(o){const n=o;t.status(502).json(be(n,s))}}),d.post("/init-instance",S,async(r,t)=>{const{agentId:e,workspacePath:s,skillEnabled:o,mcpEnabled:n,ccSwitchEnabledTools:i,skillPackage:c,skillPackages:l,mcpServers:m}=r.body||{};if(!e||!s){t.status(400).json({error:"agentId and workspacePath are required"});return}try{a.info("[init-instance] Request received",{agentId:String(e),workspacePath:String(s),skillEnabled:o,mcpEnabled:n,ccSwitchEnabledTools:i});const u=await se(e,s,{skillEnabled:o,mcpEnabled:n,ccSwitchEnabledTools:i,skillPackage:c,skillPackages:l,mcpServers:m});a.info("[init-instance] Initialization completed",{agentId:String(e),ok:u.ok,logCount:u.logs?.length||0,enabledTools:u.enabledTools}),t.json(u)}catch(u){const g=u;a.error("[init-instance] Initialization failed",{agentId:String(e),workspacePath:String(s),error:g?.message||String(u),stack:g?.stack||null}),t.status(400).json({error:g?.message||"initialize instance failed"})}}),d.post("/features",S,async(r,t)=>{const{agentId:e,features:s}=r.body||{};if(!e){t.status(400).json({error:"agentId is required"});return}ne(String(e),s),a.info("[instance/features] \u80FD\u529B\u5F00\u5173\u5DF2\u66F4\u65B0",{agentId:String(e),features:x(String(e))});let o=0;for(const n of J.getAll())if(typeof n.applyAgentFeatures=="function")try{await n.applyAgentFeatures(String(e)),o+=1}catch(i){a.error("[instance/features] adapter \u70ED\u540C\u6B65\u5931\u8D25",{agentId:String(e),providerId:n.providerId,error:i?.message||String(i)})}t.json({ok:!0,agentId:String(e),features:x(String(e)),hotSyncedAdapters:o})}),d.post("/cc-switch/state",S,async(r,t)=>{const{agentId:e}=r.body||{};if(!e){t.status(400).json({error:"agentId is required"});return}try{a.info("[cc-switch/state] Request received",{agentId:String(e)});const s=await re(e),o=await te(s),i=(await oe(e,s)).state,c=fe(i.enabledTools||[]);a.info("[cc-switch/state] Detecting SDK statuses",{agentId:String(e),detectionTargets:c});const l=await U(c);a.info("[cc-switch/state] SDK status detection completed",{agentId:String(e),toolStatus:B(l),importedMcpCount:o.mcpServers,importedSkillCount:o.skills}),t.json({ok:!0,agentId:String(e),imported:{mcpServers:o.mcpServers,skills:o.skills},state:{...i,toolStatus:l}})}catch(s){const o=s;a.error("[cc-switch/state] Failed to load state",{agentId:String(e),error:o?.message||String(s),stack:o?.stack||null}),t.status(400).json({error:o?.message||"load state failed"})}}),d.post("/cc-switch/install-tools",S,async(r,t)=>{const{agentId:e,tools:s}=r.body||{};if(!e){t.status(400).json({error:"agentId is required"});return}const o=Array.isArray(s)?s.map(c=>String(c||"").trim().toLowerCase()).filter(Boolean):[];a.info("[cc-switch/install-tools] Request received",{agentId:String(e),requestedTools:o});const n=new Set(j),i=o.filter(c=>n.has(c));if(a.info("[cc-switch/install-tools] Request normalized",{agentId:String(e),requestedTools:o,installableTools:i,supportedInstallableTools:j}),i.length===0){a.warn("[cc-switch/install-tools] No supported installable tools requested",{agentId:String(e),requestedTools:o}),t.status(400).json({error:`tools must include ${j.join(", ")}`});return}try{const c=await $(e),l=Array.from(new Set([...c.enabledTools||[],...i]));a.info("[cc-switch/install-tools] Starting installer",{agentId:String(e),installableTools:i,previousEnabledTools:c.enabledTools||[],nextEnabledTools:l});const m=await de(i);a.info("[cc-switch/install-tools] Installer completed",{agentId:String(e),toolStatus:B(m)});const u=await U(l);a.info("[cc-switch/install-tools] Refreshed enabled SDK statuses",{agentId:String(e),refreshedToolStatus:B(u)});const g={...u,...m},h=await q(e,{...c,enabledTools:l,toolStatus:g}),y=new Set(i),w=Object.entries(g).filter(([f])=>y.has(f)).filter(([,f])=>!f.installed).map(([f,k])=>({tool:f,error:k.error||null}));w.length>0&&a.warn("[cc-switch/install-tools] Some SDKs remain unavailable",{agentId:String(e),failedTools:w}),a.info("[cc-switch/install-tools] State saved",{agentId:String(e),enabledTools:h.enabledTools,toolStatus:B(g)}),t.json({ok:w.length===0,agentId:String(e),toolStatus:g,failedTools:w,state:h})}catch(c){const l=c;a.error("[cc-switch/install-tools] Install route failed",{agentId:String(e),requestedTools:o,installableTools:i,error:l?.message||String(c),stack:l?.stack||null}),t.status(400).json({error:l?.message||"install tools failed"})}}),d.post("/cc-switch/uninstall-tools",S,async(r,t)=>{const{agentId:e,tools:s}=r.body||{};if(!e){t.status(400).json({error:"agentId is required"});return}const o=Array.isArray(s)?s.map(c=>String(c||"").trim().toLowerCase()).filter(Boolean):[],n=new Set(F),i=o.filter(c=>n.has(c));if(i.length===0){t.status(400).json({error:`tools must include ${F.join(", ")}`});return}try{const c=await $(e),l=new Set(i),m=(c.enabledTools||[]).filter(k=>!l.has(String(k||"").trim().toLowerCase())),u=await ue(i),h={...await U([...m,...i]),...u},y=ke(i,h),w=y?c.enabledTools||[]:m,f=await q(e,{...c,enabledTools:w,toolStatus:h});if(y){t.status(400).json({ok:!1,agentId:String(e),error:`uninstall incomplete: ${y}`,toolStatus:h,state:f});return}t.json({ok:!0,agentId:String(e),toolStatus:h,state:f})}catch(c){const l=c;t.status(400).json({error:l?.message||"uninstall tools failed"})}}),d.post("/prepare-restart",S,async(r,t)=>{const{preserveSessions:e=!0}=r.body||{};try{a.info(`[prepare-restart] \u6536\u5230\u51C6\u5907\u91CD\u542F\u901A\u77E5\uFF0CpreserveSessions=${e}`),await z(!!e),t.json({ok:!0,preserveSessions:e,message:e?"Bridge will preserve sessions for aws-mcp-server restart":"Bridge will clean up sessions on shutdown"})}catch(s){const o=s;a.error("[prepare-restart] Error:",o),t.status(500).json({error:o.message})}});const Te={update:15*60*1e3,restart:5*60*1e3};async function Re(r){try{const t=await ae(),e=Date.now(),s=Te[r],o=[];for(const n of t.operations){if(n.operationType!==r||!["accepted","running","restarting"].includes(n.status))continue;const i=new Date(n.updatedAt).getTime();if(!Number.isNaN(i)&&e-i<=s)return{operationId:n.operationId};o.push(n.operationId)}for(const n of o)try{await ie(n),a.info(`[${r}-bridge] \u5DF2\u6E05\u7406\u8D85\u65F6\u672A\u4EA4\u4ED8\u7684\u9648\u65E7\u64CD\u4F5C: ${n}`)}catch(i){const c=i;a.warn(`[${r}-bridge] \u6E05\u7406\u9648\u65E7\u64CD\u4F5C\u5931\u8D25\uFF0C\u7EE7\u7EED\u653E\u884C: ${n}, ${c.message}`)}return null}catch(t){const e=t;return a.warn(`[${r}-bridge] \u5728\u9014\u64CD\u4F5C\u68C0\u67E5\u5931\u8D25\uFF0C\u653E\u884C\u8BF7\u6C42: ${e.message}`),null}}async function X(r,t,e){const s=await Re(r);return s?(e.status(409).json({error:t,operationId:s.operationId}),!0):!1}d.post("/update-bridge",v,async(r,t)=>{const e=P(r.body?.requestedByUserId);try{if(!e){t.status(400).json({error:"requestedByUserId is required"});return}if(await X("update","\u5B9E\u4F8B Bridge \u66F4\u65B0\u6B63\u5728\u6267\u884C\u4E2D\uFF0C\u8BF7\u52FF\u91CD\u590D\u89E6\u53D1",t))return;const s=await O(),o=C({operationType:"update",requestedByUserId:e,schedulerBaseUrl:r.body?.schedulerBaseUrl,runtimeBridgeBaseUrl:D(r),status:"accepted",message:"Bridge \u66F4\u65B0\u8BF7\u6C42\u5DF2\u6536\u5230\uFF0C\u5C06\u5728\u540E\u53F0\u6267\u884C\u5E76\u81EA\u52A8\u91CD\u542F\u3002",latestVersion:s.ok&&s.latestVersion||void 0});await b(o),me(o),t.json({ok:!0,operationId:o.operationId,message:"Bridge \u66F4\u65B0\u5DF2\u5728\u540E\u53F0\u5F00\u59CB\uFF0C\u66F4\u65B0\u5B8C\u6210\u540E\u5C06\u81EA\u52A8\u91CD\u542F\u5B9E\u4F8B Bridge\u3002"})}catch(s){const o=s;a.error("[update-bridge] Error:",o),t.status(500).json({error:o.message||"update bridge failed"})}}),d.post("/restart-bridge",v,async(r,t)=>{const{preserveSessions:e=!0}=r.body||{},s=P(r.body?.requestedByUserId);try{if(!s){t.status(400).json({error:"requestedByUserId is required"});return}if(await X("restart","\u5B9E\u4F8B Bridge \u6B63\u5728\u91CD\u542F\u4E2D\uFF0C\u8BF7\u7A0D\u5019\u518D\u8BD5",t))return;a.info(`[restart-bridge] \u6536\u5230\u5B9E\u4F8B Bridge \u91CD\u542F\u8BF7\u6C42\uFF0CpreserveSessions=${e}`);const o=C({operationType:"restart",requestedByUserId:s,schedulerBaseUrl:r.body?.schedulerBaseUrl,runtimeBridgeBaseUrl:D(r),status:"accepted",message:"Bridge \u5DF2\u6536\u5230\u91CD\u542F\u8BF7\u6C42\uFF1B\u4E0B\u6B21\u542F\u52A8\u540E\u4F1A\u53D1\u9001\u786E\u8BA4\u901A\u77E5\u3002"});await b(o),await z(!!e),t.json({ok:!0,operationId:o.operationId,preserveSessions:!!e,message:"Bridge \u6B63\u5728\u91CD\u542F\uFF0C\u670D\u52A1\u7BA1\u7406\u5668\u4F1A\u81EA\u52A8\u62C9\u8D77\u5B9E\u4F8B\uFF1B\u4E0B\u6B21\u542F\u52A8\u540E\u4F1A\u901A\u8FC7\u6D88\u606F\u4E2D\u5FC3\u901A\u77E5\u7ED3\u679C\u3002"}),a.info("[restart-bridge] \u6B63\u5728\u6267\u884C awsb service restart"),K(),setTimeout(()=>{if(process.exitCode=42,I){I("PANEL_RESTART",!!e).catch(n=>{const i=n;a.error("[restart-bridge] graceful shutdown failed:",i),process.exit(42)});return}process.exit(42)},M)}catch(o){const n=o;a.error("[restart-bridge] Error:",n),t.status(500).json({error:n.message||"restart bridge failed"})}});export{Be as buildAmbiguousSchedulerBaseUrlResponse,we as buildConnectionCheckResponse,be as buildSchedulerPingFailureResponse,ke as buildUninstallFailureMessage,d as instanceRouter,We as setGracefulShutdownFn,B as summarizeToolStatus};
|
|
2
2
|
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
export interface BgTaskOutputSubscriber {
|
|
4
|
+
onLine: (event: {
|
|
5
|
+
stream: "stdout" | "stderr";
|
|
6
|
+
text: string;
|
|
7
|
+
seq: number;
|
|
8
|
+
}) => void;
|
|
9
|
+
onDone: (event: {
|
|
10
|
+
status: string;
|
|
11
|
+
exitCode: number | null;
|
|
12
|
+
signal: string | null;
|
|
13
|
+
}) => void;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface BgTaskOutputSnapshot {
|
|
17
|
+
taskId: string;
|
|
18
|
+
status: string;
|
|
19
|
+
exitCode: number | null;
|
|
20
|
+
signal: string | null;
|
|
21
|
+
startedAt: number;
|
|
22
|
+
finishedAt: number | null;
|
|
23
|
+
command: string;
|
|
24
|
+
lines: string[];
|
|
25
|
+
nextSeq: number;
|
|
26
|
+
totalLines: number;
|
|
27
|
+
totalBytes: number;
|
|
28
|
+
truncated: boolean;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface BgManagerLike {
|
|
32
|
+
getTaskOutputSnapshot(agentId: string, taskId: string, tailLines?: number): BgTaskOutputSnapshot | null;
|
|
33
|
+
subscribeTaskOutput(agentId: string, taskId: string, subscriber: BgTaskOutputSubscriber): (() => void) | null;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export declare function getAcodeBgManager(): Promise<BgManagerLike | null>;
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import e from"node:path";import{pathToFileURL as m}from"node:url";import{getReleasedAcodeEntryPath as g}from"./acode-package.js";import{logger as o}from"../utils/logger.js";let a=null;function u(){return a||(a=(async()=>{try{const r=g(),t=e.join(e.dirname(r),"background-command-manager.js"),n=await import(m(t).href);return n.backgroundCommandManager?n.backgroundCommandManager:(o.warn("[acode-bg-manager] manager \u5355\u4F8B\u7F3A\u5931\uFF0C\u540E\u53F0\u547D\u4EE4\u8BE6\u60C5/\u6D41\u4E0D\u53EF\u7528"),null)}catch(r){return o.warn("[acode-bg-manager] \u52A0\u8F7D manager \u5931\u8D25: %s",r?.message),null}})().then(r=>(r||(a=null),r))),a}export{u as getAcodeBgManager};
|
|
2
|
+
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{resolve as g}from"node:path";import{getBrowserPool as L,disposeBrowserPool as y}from"../browser/browser-pool.js";import{getBrowserWsServer as S,stopBrowserWsServer as B}from"../browser/ws-server.js";import{createBuiltInBrowserTools as h}from"../browser/built-in-browser-tools.js";import{WS_HOST as d}from"../browser/types.js";let t=null,a=null;const x={headless:!0,stealth:!0,viewportWidth:1280,viewportHeight:
|
|
1
|
+
import{resolve as g}from"node:path";import{getBrowserPool as L,disposeBrowserPool as y}from"../browser/browser-pool.js";import{getBrowserWsServer as S,stopBrowserWsServer as B}from"../browser/ws-server.js";import{createBuiltInBrowserTools as h}from"../browser/built-in-browser-tools.js";import{WS_HOST as d}from"../browser/types.js";let t=null,a=null;const x={headless:!0,stealth:!0,viewportWidth:1280,viewportHeight:720,maxPagesPerSession:5,maxSessionDurationMs:0,idleTimeoutMs:0,profileDir:T()},W={port:v(),host:d,heartbeatMs:30*1e3,idleTimeoutMs:30*60*1e3};function v(){const r=process.env.AWS_BROWSER_WS_PORT;if(r){const e=parseInt(r,10);if(Number.isFinite(e)&&e>0&&e<65536)return e}return 7891}function T(){const r=process.env.AWS_BROWSER_PROFILE_DIR;return r||g(process.cwd(),".browser-profiles")}async function P(r,e){return t||a||(a=(async()=>{const o=L({...x,...r}),s=await S(o,{...W,...e}),{port:n,host:i}=await s.start();o.setListenerTriggerHook((c,f,l,m)=>{try{s.broadcastListenerTriggered(c,f,{listenerId:l.listenerId,selector:m.selector,prompt:l.prompt,url:l.url,text:l.text??"",at:l.triggeredAt,changeType:l.changeType})}catch{}u(c,f).catch(()=>{});try{w.get(c)?.()}catch{}});const p={pool:o,wsServer:s,wsPort:n,wsHost:i};return t=p,a=null,p})(),a)}function O(r,e){return!t||!r?null:t.wsServer.getUrl(r,e)}function U(r){return!t||!r?[]:t.pool.listSessions(r).map(o=>({browserId:o.browserId,title:o.title,wsUrl:t.wsServer.getUrl(r,o.browserId)}))}function b(){return t?.wsPort??null}async function H(r,e,o){return r?(await P(),t?h({pool:t.pool,wsServer:t.wsServer,agentId:r,scopeKey:e,ssrfConfig:o}):[]):[]}async function R(r){if(!(!t||!r))try{await t.pool.closeByAgent(r)}catch{}}async function k(r,e){if(!(!t||!r))try{await t.pool.close(r,e)}catch{}}async function D(){if(t)try{await B(),await y()}finally{t=null,a=null}}function F(){return t!==null}async function C(r,e,o){if(!t||!r)throw new Error("browser manager not started");const s=t.pool.get(r,e);if(!s)throw new Error(`Browser session not launched for ${r}/${e}. Call browser_launch first.`);const n=await s.listenerManager.addListener(o);return await u(r,e),n}async function N(r,e,o,s){if(!t||!r)return;const n=t.pool.get(r,e);if(!n)return;const i=await n.listenerManager.updateListener({...s,listenerId:o});return await u(r,e),i}async function G(r,e,o){if(!t||!r)return!1;const s=t.pool.get(r,e);if(!s)return!1;const n=await s.listenerManager.removeListener(o);return await u(r,e),n}async function j(r,e,o,s){if(!t||!r)return;const n=t.pool.get(r,e);if(!n)return;const i=await n.listenerManager.toggleListener(o,s);return await u(r,e),i}async function Y(r,e){if(!t||!r)return[];const o=t.pool.get(r,e);return o?o.listenerManager.listListeners():[]}async function u(r,e){if(!t)return;const o=t.pool.get(r,e);if(o)try{const s=await o.listenerManager.listListeners();t.wsServer.broadcastListenerUpdate(r,e,s)}catch{}}async function q(r){return!t||!r?[]:t.pool.collectListenerEvents(r)}const w=new Map;function z(r,e){r&&w.set(r,e)}function J(r,e){r&&(e&&w.get(r)!==e||w.delete(r))}function K(r){if(r.length===0)return"";const e={added:"\u65B0\u589E",removed:"\u5220\u9664",changed:"\u6539\u53D8"};return`\u7CFB\u7EDF\u63D0\u9192\uFF1A\u4EE5\u4E0B\u6D4F\u89C8\u5668\u76D1\u542C\u5668\u5DF2\u89E6\u53D1\uFF0C\u8BF7\u7ACB\u5373\u5904\u7406\uFF1A
|
|
2
2
|
|
|
3
3
|
`+r.map((s,n)=>{const i=s.browserId&&s.browserId!=="default"?`\u6D4F\u89C8\u5668[${s.browserId}]`:"\u6D4F\u89C8\u5668",p=s.selector?`\u5143\u7D20\u9009\u62E9\u5668: ${s.selector}
|
|
4
4
|
`:"",c=e[s.changeType??"changed"]??"\u6539\u53D8",f=new Date(s.triggeredAt).toISOString();return`\u3010\u76D1\u542C\u5668\u4E8B\u4EF6 ${n+1} | ${i} | URL: ${s.url}\u3011
|