aws-runtime-bridge 1.9.98 → 1.9.100

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (120) hide show
  1. package/dist/adapter/AcodeSdkAdapter.d.ts +7 -1
  2. package/dist/adapter/AcodeSdkAdapter.js +5 -5
  3. package/dist/adapter/ClaudeSdkAdapter.d.ts +4 -0
  4. package/dist/adapter/ClaudeSdkAdapter.js +2 -2
  5. package/dist/adapter/CodexSdkAdapter.d.ts +2 -0
  6. package/dist/adapter/CodexSdkAdapter.js +3 -3
  7. package/dist/adapter/OpencodeSdkAdapter.d.ts +2 -0
  8. package/dist/adapter/OpencodeSdkAdapter.js +2 -2
  9. package/dist/adapter/idle-poll-loop.d.ts +4 -1
  10. package/dist/adapter/idle-poll-loop.js +1 -1
  11. package/dist/adapter/types.d.ts +4 -2
  12. package/dist/browser/browser-pool.d.ts +73 -0
  13. package/dist/browser/browser-pool.js +2 -0
  14. package/dist/browser/browser-profile-store.d.ts +28 -0
  15. package/dist/browser/browser-profile-store.js +2 -0
  16. package/dist/browser/built-in-browser-tools.d.ts +27 -0
  17. package/dist/browser/built-in-browser-tools.js +2 -0
  18. package/dist/browser/element-locator.d.ts +24 -0
  19. package/dist/browser/element-locator.js +2 -0
  20. package/dist/browser/element-picker.d.ts +17 -0
  21. package/dist/browser/element-picker.js +2 -0
  22. package/dist/browser/input-dispatcher.d.ts +34 -0
  23. package/dist/browser/input-dispatcher.js +2 -0
  24. package/dist/browser/listener-store.d.ts +32 -0
  25. package/dist/browser/listener-store.js +2 -0
  26. package/dist/browser/page-listener-manager.d.ts +106 -0
  27. package/dist/browser/page-listener-manager.js +307 -0
  28. package/dist/browser/page-listener-types.d.ts +90 -0
  29. package/dist/browser/page-listener-types.js +1 -0
  30. package/dist/browser/screencast-manager.d.ts +42 -0
  31. package/dist/browser/screencast-manager.js +2 -0
  32. package/dist/browser/snapshot-serializer.d.ts +15 -0
  33. package/dist/browser/snapshot-serializer.js +5 -0
  34. package/dist/browser/ssrf-guard.d.ts +15 -0
  35. package/dist/browser/ssrf-guard.js +2 -0
  36. package/dist/browser/stealth.d.ts +7 -0
  37. package/dist/browser/stealth.js +173 -0
  38. package/dist/browser/step-archive.d.ts +52 -0
  39. package/dist/browser/step-archive.js +2 -0
  40. package/dist/browser/types.d.ts +192 -0
  41. package/dist/browser/types.js +2 -0
  42. package/dist/browser/ws-server.d.ts +67 -0
  43. package/dist/browser/ws-server.js +2 -0
  44. package/dist/index.js +1 -1
  45. package/dist/middleware/auth.d.ts +2 -0
  46. package/dist/middleware/auth.js +1 -1
  47. package/dist/remote-desktop/computer-control.d.ts +30 -0
  48. package/dist/remote-desktop/computer-control.js +2 -0
  49. package/dist/remote-desktop/coords.d.ts +13 -0
  50. package/dist/remote-desktop/coords.js +2 -0
  51. package/dist/remote-desktop/frame-backpressure.d.ts +7 -0
  52. package/dist/remote-desktop/frame-backpressure.js +2 -0
  53. package/dist/remote-desktop/index.d.ts +7 -0
  54. package/dist/remote-desktop/index.js +4 -0
  55. package/dist/remote-desktop/input-backends/interface.d.ts +17 -0
  56. package/dist/remote-desktop/input-backends/interface.js +1 -0
  57. package/dist/remote-desktop/input-backends/key-map.d.ts +2 -0
  58. package/dist/remote-desktop/input-backends/key-map.js +2 -0
  59. package/dist/remote-desktop/input-dispatcher.d.ts +11 -0
  60. package/dist/remote-desktop/input-dispatcher.js +2 -0
  61. package/dist/remote-desktop/permission-manager.d.ts +7 -0
  62. package/dist/remote-desktop/permission-manager.js +2 -0
  63. package/dist/remote-desktop/screen-capture.d.ts +27 -0
  64. package/dist/remote-desktop/screen-capture.js +2 -0
  65. package/dist/remote-desktop/session-manager.d.ts +59 -0
  66. package/dist/remote-desktop/session-manager.js +2 -0
  67. package/dist/remote-desktop/system-backend.d.ts +65 -0
  68. package/dist/remote-desktop/system-backend.js +5 -0
  69. package/dist/remote-desktop/types.d.ts +68 -0
  70. package/dist/remote-desktop/types.js +1 -0
  71. package/dist/remote-desktop/ui-automation.d.ts +48 -0
  72. package/dist/remote-desktop/ui-automation.js +31 -0
  73. package/dist/remote-desktop/win32-api.d.ts +29 -0
  74. package/dist/remote-desktop/win32-api.js +2 -0
  75. package/dist/routes/background-tasks.d.ts +2 -0
  76. package/dist/routes/background-tasks.js +2 -0
  77. package/dist/routes/browser-listeners.d.ts +2 -0
  78. package/dist/routes/browser-listeners.js +2 -0
  79. package/dist/routes/browser-sessions.d.ts +2 -0
  80. package/dist/routes/browser-sessions.js +2 -0
  81. package/dist/routes/pty.js +3 -3
  82. package/dist/routes/terminal.js +30 -30
  83. package/dist/services/agent-pty-manager.d.ts +22 -0
  84. package/dist/services/agent-pty-manager.js +5 -0
  85. package/dist/services/browser-session-manager.d.ts +55 -0
  86. package/dist/services/browser-session-manager.js +12 -0
  87. package/dist/services/cli-commands.d.ts +4 -0
  88. package/dist/services/cli-commands.js +5 -5
  89. package/package/acode/dist/await-complete-tool.d.ts +17 -0
  90. package/package/acode/dist/await-complete-tool.js +21 -0
  91. package/package/acode/dist/background-command-manager.d.ts +148 -0
  92. package/package/acode/dist/background-command-manager.js +8 -0
  93. package/package/acode/dist/background-command-store.d.ts +46 -0
  94. package/package/acode/dist/background-command-store.js +2 -0
  95. package/package/acode/dist/background-command-tools.d.ts +5 -0
  96. package/package/acode/dist/background-command-tools.js +18 -0
  97. package/package/acode/dist/background-output-ringbuffer.d.ts +23 -0
  98. package/package/acode/dist/background-output-ringbuffer.js +3 -0
  99. package/package/acode/dist/built-in-file-tools.d.ts +8 -1
  100. package/package/acode/dist/built-in-file-tools.js +2 -2
  101. package/package/acode/dist/compaction.js +6 -6
  102. package/package/acode/dist/index.d.ts +1 -0
  103. package/package/acode/dist/index.js +1 -1
  104. package/package/acode/dist/runtime.d.ts +6 -0
  105. package/package/acode/dist/runtime.js +27 -27
  106. package/package/acode/dist/sub-agent-tools.d.ts +0 -1
  107. package/package/acode/dist/sub-agent-tools.js +11 -28
  108. package/package/acode/dist/terminal-tool.d.ts +7 -0
  109. package/package/acode/dist/terminal-tool.js +2 -0
  110. package/package/acode/dist/terminal-types.d.ts +38 -0
  111. package/package/acode/dist/terminal-types.js +1 -0
  112. package/package/acode/dist/types.d.ts +10 -1
  113. package/package/aws-client-agent-mcp/dist/agent-client.d.ts +3 -1
  114. package/package/aws-client-agent-mcp/dist/agent-client.js +1 -1
  115. package/package/aws-client-agent-mcp/dist/logger.d.ts +2 -0
  116. package/package/aws-client-agent-mcp/dist/logger.js +1 -1
  117. package/package/aws-client-agent-mcp/dist/mcp-server.d.ts +8 -0
  118. package/package/aws-client-agent-mcp/dist/mcp-server.js +30 -19
  119. package/package/aws-client-agent-mcp/dist/types.d.ts +25 -0
  120. package/package.json +6 -2
@@ -1,4 +1,4 @@
1
- import{spawn as A}from"node:child_process";import{EventEmitter as E}from"node:events";import*as y from"node:fs";import*as x from"node:net";import*as O from"node:os";import*as h from"node:path";import{v4 as k}from"uuid";import{BRIDGE_PACKAGE_ROOT as T,importBridgeSdkPackage as _}from"../utils/sdk-package-loader.js";import{logger as $}from"../utils/logger.js";import{IdlePollLoop as D}from"./idle-poll-loop.js";import{getToolActionInfo as P,redactTransientClipboardPastePaths as C,redactTransientClipboardPastePathsInValue as I}from"./types.js";function M(d){if(d&&h.isAbsolute(d)&&y.existsSync(d))return d;const t=d?.trim()||"opencode";if(process.platform==="win32"){const e=process.env.USERPROFILE||process.env.HOME||O.homedir(),o=process.env.LOCALAPPDATA||"",r=[h.join(o,"Volta","tools","shared","opencode-ai","node_modules","opencode-windows-x64","bin"),h.join(o,"Volta","tools","shared","opencode-ai","node_modules","opencode-windows-x64-baseline","bin")];for(const s of r){const n=h.join(s,"opencode.exe");if(y.existsSync(n))return console.log(`[OpencodeSdkAdapter] Found opencode.exe: ${n}`),n}const i=[h.join(e,"AppData","Roaming","npm"),h.join(e,"AppData","Local","Volta","bin")];for(const s of i)for(const n of["opencode.cmd","opencode.exe","opencode"]){const a=h.join(s,n);if(y.existsSync(a))return console.log(`[OpencodeSdkAdapter] Found ${n}: ${a}`),a}}return t}async function L(d=14096){for(let t=d;t<d+100;t++)if(await new Promise(o=>{const r=x.createServer();r.once("error",()=>o(!1)),r.once("listening",()=>{r.close(()=>o(!0))}),r.listen(t,"127.0.0.1")}))return t;throw new Error(`No available port found in range ${d}\u2013${d+99}`)}function j(d){return d instanceof Error&&"code"in d&&typeof d.code=="string"}function N(d,t){return j(d)&&d.code==="ERR_MODULE_NOT_FOUND"&&d.message.includes(t)}function v(d){if(!d||!d.trim())return;const t=d.trim(),e=t.indexOf("/");return e>0?{providerID:t.slice(0,e),modelID:t.slice(e+1)}:{modelID:t}}class W extends E{constructor(){super(...arguments),this.providerId="opencode",this.displayName="OpenCode",this.sessions=new Map}async startSession(t,e){console.log(`[OpencodeSdkAdapter] \u2605\u2605\u2605 \u542F\u52A8 OpenCode SDK \u4F1A\u8BDD \u2605\u2605\u2605 sessionId="${t}", workingDirectory="${e.workingDirectory}"`);const o={adapterSession:{sessionId:t,status:"starting",messages:[],createdAt:new Date().toISOString(),totalUsage:{inputTokens:0,outputTokens:0}},config:e,sseActive:!1,pendingPermissions:new Map,emittedToolStarts:new Set,workingDirectory:e.workingDirectory,userMessageIds:new Set,currentAssistantText:"",seenTextPartIds:new Set};this.sessions.set(t,o),this.emit("status-change",t,"starting");try{const r=await L();o.port=r,console.log(`[OpencodeSdkAdapter] \u2605\u2605\u2605 \u4F7F\u7528\u7AEF\u53E3 \u2605\u2605\u2605 port=${r}`);const i=M(e.command);console.log(`[OpencodeSdkAdapter] \u2605\u2605\u2605 \u542F\u52A8\u547D\u4EE4 \u2605\u2605\u2605 command="${i}"`);const s=i.endsWith(".cmd")||i.endsWith(".CMD")||process.platform==="win32"&&!h.isAbsolute(i);console.log(`[OpencodeSdkAdapter] shell=${s}`);const n={...process.env,...e.envOverrides},a={};v(e.model)?.modelID&&(a.model=e.model.trim()),e.systemPrompt&&typeof e.systemPrompt=="string"&&(a.systemPrompt=e.systemPrompt),Object.keys(a).length>0&&(n.OPENCODE_CONFIG_CONTENT=JSON.stringify(a),$.info(`[OpencodeSdkAdapter] OPENCODE_CONFIG_CONTENT keys=${Object.keys(a).join(",")}`));const p=A(i,["serve","--port",String(r)],{cwd:e.workingDirectory,stdio:["ignore","pipe","pipe"],env:n,shell:s});o.serverProcess=p,o.startupOutput=[],p.stdout?.on("data",l=>{const f=l.toString();this.recordStartupOutput(o,f),console.debug(`[OpencodeSdkAdapter] serve stdout: ${f.slice(0,200)}`)}),p.stderr?.on("data",l=>{const f=l.toString();this.recordStartupOutput(o,f),console.debug(`[OpencodeSdkAdapter] serve stderr: ${f.slice(0,200)}`)}),p.on("error",l=>{console.error("[OpencodeSdkAdapter] Server process error:",l),o.adapterSession.status!=="terminated"&&(o.adapterSession.status="error",this.emit("status-change",t,"error"),this.emitEvent({type:"error",sessionId:t,timestamp:new Date().toISOString(),data:{text:`OpenCode server error: ${l.message}`}}))}),p.on("exit",l=>{console.log(`[OpencodeSdkAdapter] Server exited with code ${l}`),o.sseActive=!1});const c=new Promise(l=>{p.once("exit",(f,b)=>l({code:f,signal:b}))}),g=(await this.loadSdk()).createOpencodeClient({baseUrl:`http://127.0.0.1:${r}`});o.client=g,await Promise.race([this.waitForServer(g,e.workingDirectory,1e4,r),c.then(l=>{throw new Error(this.formatStartupExitMessage(l,o.startupOutput))})]),console.log(`[OpencodeSdkAdapter] \u2605\u2605\u2605 OpenCode server ready \u2605\u2605\u2605 port=${r}`);const w=(await g.session.create({body:{title:`aws-bridge-${t.slice(0,8)}`},query:{directory:e.workingDirectory}})).data;if(!w?.id)throw new Error("OpenCode session.create() did not return an ID");o.opencodeSessionId=w.id,o.adapterSession.providerSessionId=w.id,console.log(`[OpencodeSdkAdapter] \u2605\u2605\u2605 OpenCode \u4F1A\u8BDD\u521B\u5EFA\u6210\u529F \u2605\u2605\u2605 sessionId="${w.id}"`),this.startSseLoop(t,o),o.adapterSession.status="waiting_input",this.emit("status-change",t,"waiting_input",{usage:o.adapterSession.totalUsage}),e.initialPrompt&&this.dispatchInitialPrompt(t,e.initialPrompt)}catch(r){const i=r instanceof Error?r.message:String(r);throw console.error("[OpencodeSdkAdapter] startSession failed:",r),o.adapterSession.status="error",this.emit("status-change",t,"error"),this.emitEvent({type:"error",sessionId:t,timestamp:new Date().toISOString(),data:{text:`OpenCode start failed: ${i}`}}),r}}async sendMessage(t,e,o){const r=this.sessions.get(t);if(!r)throw new Error(`Session ${t} not found`);if(!r.client||!r.opencodeSessionId)throw new Error(`Session ${t} not ready`);const i={id:k(),sessionId:t,role:"user",content:e,timestamp:new Date().toISOString()};r.adapterSession.messages.push(i),this.emit("conversation-message",t,i),o?.keepWaitingStatus||(r.adapterSession.status="thinking",this.emit("status-change",t,"thinking"),this.stopIdleDetection(t));try{const s=v(r.config.model),n={parts:[{type:"text",text:e}]};s?.providerID&&s?.modelID&&(n.model={providerID:s.providerID,modelID:s.modelID},console.log(`[OpencodeSdkAdapter] \u2605\u2605\u2605 per-message model \u2605\u2605\u2605 provider=${s.providerID}, model=${s.modelID}`)),await r.client.session.prompt({path:{id:r.opencodeSessionId},body:n,query:{directory:r.workingDirectory}})}catch(s){const n=s instanceof Error?s.message:String(s);this.emitEvent({type:"error",sessionId:t,timestamp:new Date().toISOString(),data:{text:n}})}}async sendConfirmation(t,e){const o=this.sessions.get(t);if(!o?.client||!o.opencodeSessionId)return;const r=e?"once":"reject",i=o.opencodeSessionId;for(const[s]of o.pendingPermissions)try{await o.client.postSessionIdPermissionsPermissionId({path:{id:i,permissionID:s},body:{response:r},query:{directory:o.workingDirectory}})}catch(n){console.warn("[OpencodeSdkAdapter] Permission response failed:",n)}o.pendingPermissions.clear()}async sendQuestionAnswer(t,e){}setIdleCommands(t,e){const o=this.sessions.get(t);o&&(o.idleCommands=e,o.adapterSession.status==="waiting_input"&&(e.idleInputCommand||e.nonInputCommand)&&this.startIdleDetection(t))}async abortCurrentTurn(t){const e=this.sessions.get(t);if(!(!e?.client||!e.opencodeSessionId))try{await e.client.session.abort({path:{id:e.opencodeSessionId},query:{directory:e.workingDirectory}})}catch(o){console.warn("[OpencodeSdkAdapter] Abort failed:",o)}}async terminateSession(t){const e=this.sessions.get(t);if(e){if(this.stopIdleDetection(t),e.sseActive=!1,e.sseAbortController?.abort(),e.serverProcess)try{e.serverProcess.kill()}catch{}e.adapterSession.status="terminated",this.emit("status-change",t,"terminated"),this.emitEvent({type:"session_complete",sessionId:t,timestamp:new Date().toISOString(),data:{exitCode:0}}),this.sessions.delete(t)}}getConversation(t){return this.sessions.get(t)?.adapterSession.messages??[]}hasSession(t){return this.sessions.has(t)}getProviderSessionId(t){return this.sessions.get(t)?.opencodeSessionId}getSessionStatus(t){return this.sessions.get(t)?.adapterSession.status}getSessionPid(t){const e=this.sessions.get(t);if(e)return e.serverProcess?.pid}cleanup(){for(const[t]of this.sessions)try{this.terminateSession(t)}catch{}this.sessions.clear()}async loadSdk(){if(!this.sdkModule)try{this.sdkModule=await _("@opencode-ai/sdk")}catch(t){throw N(t,"@opencode-ai/sdk")?new Error(["OpenCode SDK provider is not installed.","","aws-runtime-bridge keeps Codex/OpenCode provider SDKs optional to reduce default install size.",'To use SDK mode with command "opencode", install the SDK into the aws-runtime-bridge package root:',"",` npm install --prefix "${T}" @opencode-ai/sdk@latest opencode-ai@latest`,"","If aws-runtime-bridge is installed locally, run the same command with that local package path.","",`Original error: ${t instanceof Error?t.message:String(t)}`].join(`
1
+ import{spawn as E}from"node:child_process";import{EventEmitter as x}from"node:events";import*as w from"node:fs";import*as T from"node:net";import*as O from"node:os";import*as h from"node:path";import{v4 as y}from"uuid";import{BRIDGE_PACKAGE_ROOT as _,importBridgeSdkPackage as $}from"../utils/sdk-package-loader.js";import{logger as M}from"../utils/logger.js";import{IdlePollLoop as D}from"./idle-poll-loop.js";import{getToolActionInfo as P,redactTransientClipboardPastePaths as C,redactTransientClipboardPastePathsInValue as I}from"./types.js";import{registerListenerWakeHook as L,unregisterListenerWakeHook as v}from"../services/browser-session-manager.js";function j(d){if(d&&h.isAbsolute(d)&&w.existsSync(d))return d;const t=d?.trim()||"opencode";if(process.platform==="win32"){const e=process.env.USERPROFILE||process.env.HOME||O.homedir(),o=process.env.LOCALAPPDATA||"",r=[h.join(o,"Volta","tools","shared","opencode-ai","node_modules","opencode-windows-x64","bin"),h.join(o,"Volta","tools","shared","opencode-ai","node_modules","opencode-windows-x64-baseline","bin")];for(const s of r){const n=h.join(s,"opencode.exe");if(w.existsSync(n))return console.log(`[OpencodeSdkAdapter] Found opencode.exe: ${n}`),n}const i=[h.join(e,"AppData","Roaming","npm"),h.join(e,"AppData","Local","Volta","bin")];for(const s of i)for(const n of["opencode.cmd","opencode.exe","opencode"]){const a=h.join(s,n);if(w.existsSync(a))return console.log(`[OpencodeSdkAdapter] Found ${n}: ${a}`),a}}return t}async function N(d=14096){for(let t=d;t<d+100;t++)if(await new Promise(o=>{const r=T.createServer();r.once("error",()=>o(!1)),r.once("listening",()=>{r.close(()=>o(!0))}),r.listen(t,"127.0.0.1")}))return t;throw new Error(`No available port found in range ${d}\u2013${d+99}`)}function R(d){return d instanceof Error&&"code"in d&&typeof d.code=="string"}function U(d,t){return R(d)&&d.code==="ERR_MODULE_NOT_FOUND"&&d.message.includes(t)}function b(d){if(!d||!d.trim())return;const t=d.trim(),e=t.indexOf("/");return e>0?{providerID:t.slice(0,e),modelID:t.slice(e+1)}:{modelID:t}}class B extends x{constructor(){super(...arguments),this.providerId="opencode",this.displayName="OpenCode",this.sessions=new Map,this.listenerWakeHooks=new Map}async startSession(t,e){console.log(`[OpencodeSdkAdapter] \u2605\u2605\u2605 \u542F\u52A8 OpenCode SDK \u4F1A\u8BDD \u2605\u2605\u2605 sessionId="${t}", workingDirectory="${e.workingDirectory}"`);const o={adapterSession:{sessionId:t,status:"starting",messages:[],createdAt:new Date().toISOString(),totalUsage:{inputTokens:0,outputTokens:0}},config:e,sseActive:!1,pendingPermissions:new Map,emittedToolStarts:new Set,workingDirectory:e.workingDirectory,userMessageIds:new Set,currentAssistantText:"",seenTextPartIds:new Set};if(this.sessions.set(t,o),e.agentId){const r=()=>{this.sessions.get(t)?.idlePollLoop?.start()};this.listenerWakeHooks.set(t,r),L(e.agentId,r)}this.emit("status-change",t,"starting");try{const r=await N();o.port=r,console.log(`[OpencodeSdkAdapter] \u2605\u2605\u2605 \u4F7F\u7528\u7AEF\u53E3 \u2605\u2605\u2605 port=${r}`);const i=j(e.command);console.log(`[OpencodeSdkAdapter] \u2605\u2605\u2605 \u542F\u52A8\u547D\u4EE4 \u2605\u2605\u2605 command="${i}"`);const s=i.endsWith(".cmd")||i.endsWith(".CMD")||process.platform==="win32"&&!h.isAbsolute(i);console.log(`[OpencodeSdkAdapter] shell=${s}`);const n={...process.env,...e.envOverrides},a={};b(e.model)?.modelID&&(a.model=e.model.trim()),e.systemPrompt&&typeof e.systemPrompt=="string"&&(a.systemPrompt=e.systemPrompt),Object.keys(a).length>0&&(n.OPENCODE_CONFIG_CONTENT=JSON.stringify(a),M.info(`[OpencodeSdkAdapter] OPENCODE_CONFIG_CONTENT keys=${Object.keys(a).join(",")}`));const l=E(i,["serve","--port",String(r)],{cwd:e.workingDirectory,stdio:["ignore","pipe","pipe"],env:n,shell:s});o.serverProcess=l,o.startupOutput=[],l.stdout?.on("data",c=>{const f=c.toString();this.recordStartupOutput(o,f),console.debug(`[OpencodeSdkAdapter] serve stdout: ${f.slice(0,200)}`)}),l.stderr?.on("data",c=>{const f=c.toString();this.recordStartupOutput(o,f),console.debug(`[OpencodeSdkAdapter] serve stderr: ${f.slice(0,200)}`)}),l.on("error",c=>{console.error("[OpencodeSdkAdapter] Server process error:",c),o.adapterSession.status!=="terminated"&&(o.adapterSession.status="error",this.emit("status-change",t,"error"),this.emitEvent({type:"error",sessionId:t,timestamp:new Date().toISOString(),data:{text:`OpenCode server error: ${c.message}`}}))}),l.on("exit",c=>{console.log(`[OpencodeSdkAdapter] Server exited with code ${c}`),o.sseActive=!1});const p=new Promise(c=>{l.once("exit",(f,A)=>c({code:f,signal:A}))}),g=(await this.loadSdk()).createOpencodeClient({baseUrl:`http://127.0.0.1:${r}`});o.client=g,await Promise.race([this.waitForServer(g,e.workingDirectory,1e4,r),p.then(c=>{throw new Error(this.formatStartupExitMessage(c,o.startupOutput))})]),console.log(`[OpencodeSdkAdapter] \u2605\u2605\u2605 OpenCode server ready \u2605\u2605\u2605 port=${r}`);const k=(await g.session.create({body:{title:`aws-bridge-${t.slice(0,8)}`},query:{directory:e.workingDirectory}})).data;if(!k?.id)throw new Error("OpenCode session.create() did not return an ID");o.opencodeSessionId=k.id,o.adapterSession.providerSessionId=k.id,console.log(`[OpencodeSdkAdapter] \u2605\u2605\u2605 OpenCode \u4F1A\u8BDD\u521B\u5EFA\u6210\u529F \u2605\u2605\u2605 sessionId="${k.id}"`),this.startSseLoop(t,o),o.adapterSession.status="waiting_input",this.emit("status-change",t,"waiting_input",{usage:o.adapterSession.totalUsage}),e.initialPrompt&&this.dispatchInitialPrompt(t,e.initialPrompt)}catch(r){e.agentId&&v(e.agentId,this.listenerWakeHooks.get(t)),this.listenerWakeHooks.delete(t);const i=r instanceof Error?r.message:String(r);throw console.error("[OpencodeSdkAdapter] startSession failed:",r),o.adapterSession.status="error",this.emit("status-change",t,"error"),this.emitEvent({type:"error",sessionId:t,timestamp:new Date().toISOString(),data:{text:`OpenCode start failed: ${i}`}}),r}}async sendMessage(t,e,o){const r=this.sessions.get(t);if(!r)throw new Error(`Session ${t} not found`);if(!r.client||!r.opencodeSessionId)throw new Error(`Session ${t} not ready`);const i={id:y(),sessionId:t,role:"user",content:e,timestamp:new Date().toISOString()};r.adapterSession.messages.push(i),this.emit("conversation-message",t,i),o?.keepWaitingStatus||(r.adapterSession.status="thinking",this.emit("status-change",t,"thinking"),this.stopIdleDetection(t));try{const s=b(r.config.model),n={parts:[{type:"text",text:e}]};s?.providerID&&s?.modelID&&(n.model={providerID:s.providerID,modelID:s.modelID},console.log(`[OpencodeSdkAdapter] \u2605\u2605\u2605 per-message model \u2605\u2605\u2605 provider=${s.providerID}, model=${s.modelID}`)),await r.client.session.prompt({path:{id:r.opencodeSessionId},body:n,query:{directory:r.workingDirectory}})}catch(s){const n=s instanceof Error?s.message:String(s);this.emitEvent({type:"error",sessionId:t,timestamp:new Date().toISOString(),data:{text:n}})}}async sendConfirmation(t,e){const o=this.sessions.get(t);if(!o?.client||!o.opencodeSessionId)return;const r=e?"once":"reject",i=o.opencodeSessionId;for(const[s]of o.pendingPermissions)try{await o.client.postSessionIdPermissionsPermissionId({path:{id:i,permissionID:s},body:{response:r},query:{directory:o.workingDirectory}})}catch(n){console.warn("[OpencodeSdkAdapter] Permission response failed:",n)}o.pendingPermissions.clear()}async sendQuestionAnswer(t,e){}setIdleCommands(t,e){const o=this.sessions.get(t);o&&(o.idleCommands=e,o.adapterSession.status==="waiting_input"&&(e.idleInputCommand||e.nonInputCommand)&&this.startIdleDetection(t))}async abortCurrentTurn(t){const e=this.sessions.get(t);if(!(!e?.client||!e.opencodeSessionId))try{await e.client.session.abort({path:{id:e.opencodeSessionId},query:{directory:e.workingDirectory}})}catch(o){console.warn("[OpencodeSdkAdapter] Abort failed:",o)}}async terminateSession(t){const e=this.sessions.get(t);if(e){if(this.stopIdleDetection(t),e.sseActive=!1,e.sseAbortController?.abort(),e.serverProcess)try{e.serverProcess.kill()}catch{}e.adapterSession.status="terminated",this.emit("status-change",t,"terminated"),this.emitEvent({type:"session_complete",sessionId:t,timestamp:new Date().toISOString(),data:{exitCode:0}}),e.config.agentId&&v(e.config.agentId,this.listenerWakeHooks.get(t)),this.listenerWakeHooks.delete(t),this.sessions.delete(t)}}getConversation(t){return this.sessions.get(t)?.adapterSession.messages??[]}hasSession(t){return this.sessions.has(t)}getProviderSessionId(t){return this.sessions.get(t)?.opencodeSessionId}getSessionStatus(t){return this.sessions.get(t)?.adapterSession.status}getSessionPid(t){const e=this.sessions.get(t);if(e)return e.serverProcess?.pid}cleanup(){for(const[t]of this.sessions)try{this.terminateSession(t)}catch{}this.sessions.clear()}async loadSdk(){if(!this.sdkModule)try{this.sdkModule=await $("@opencode-ai/sdk")}catch(t){throw U(t,"@opencode-ai/sdk")?new Error(["OpenCode SDK provider is not installed.","","aws-runtime-bridge keeps Codex/OpenCode provider SDKs optional to reduce default install size.",'To use SDK mode with command "opencode", install the SDK into the aws-runtime-bridge package root:',"",` npm install --prefix "${_}" @opencode-ai/sdk@latest opencode-ai@latest`,"","If aws-runtime-bridge is installed locally, run the same command with that local package path.","",`Original error: ${t instanceof Error?t.message:String(t)}`].join(`
2
2
  `)):t}return this.sdkModule}async waitForServer(t,e,o,r){const i=Date.now()+o;for(;Date.now()<i;){try{if((await t.session.list({query:{directory:e}})).data!==void 0)return}catch{}await new Promise(s=>setTimeout(s,300))}throw new Error(`OpenCode server (port ${r}) did not start within ${o}ms`)}recordStartupOutput(t,e){const o=e.trim();o&&(t.startupOutput=[...t.startupOutput??[],o].slice(-8))}formatStartupExitMessage(t,e){const o=t.signal?`signal ${t.signal}`:`code ${t.code??"unknown"}`,r=(e??[]).join(`
3
- `).trim();return r?`OpenCode server exited during startup (${o}): ${r}`:`OpenCode server exited during startup (${o}). Please check whether opencode serve can run in this workspace.`}dispatchInitialPrompt(t,e){this.sendMessage(t,e).catch(o=>{const r=o instanceof Error?o.message:String(o);this.emitEvent({type:"error",sessionId:t,timestamp:new Date().toISOString(),data:{text:`OpenCode initial prompt failed: ${r}`}})})}startSseLoop(t,e){e.sseActive=!0;const o=new AbortController;e.sseAbortController=o,this.runSseLoop(t,e,o).catch(r=>{o.signal.aborted||console.error("[OpencodeSdkAdapter] SSE loop error:",r)})}async runSseLoop(t,e,o){if(e.client)try{const r=await e.client.event.subscribe({query:{directory:e.workingDirectory},signal:o.signal});for await(const i of r.stream){if(!e.sseActive||o.signal.aborted)break;this.handleOpenCodeEvent(t,e,i)}}catch(r){if(o.signal.aborted)return;console.error("[OpencodeSdkAdapter] SSE stream ended:",r)}}handleOpenCodeEvent(t,e,o){const r=new Date().toISOString(),i=e.opencodeSessionId;switch(o.type){case"message.updated":{const s=o.properties.info;if(s.sessionID!==i)break;if(s.role==="user")e.userMessageIds.add(s.id);else if(s.role==="assistant"){const n=s.tokens;n&&(e.adapterSession.totalUsage.inputTokens=Math.max(e.adapterSession.totalUsage.inputTokens,n.input),e.adapterSession.totalUsage.outputTokens=Math.max(e.adapterSession.totalUsage.outputTokens,n.output))}break}case"message.part.updated":{const{part:s,delta:n}=o.properties;if(s.sessionID!==i||e.userMessageIds.has(s.messageID))break;this.handlePartUpdate(t,e,s,n,r);break}case"session.idle":{if(o.properties.sessionID!==i)break;if(e.currentAssistantText){const a={id:k(),sessionId:t,role:"assistant",content:e.currentAssistantText,timestamp:r};e.adapterSession.messages.push(a),e.currentAssistantText=""}e.seenTextPartIds.clear(),this.emitEvent({type:"turn_complete",sessionId:t,timestamp:r,data:{usage:e.adapterSession.totalUsage}}),e.adapterSession.status="waiting_input";const s=e.idleCommands?.idleInputCommand;!!(s&&this.isMessagePollingIdleCommand(s))?this.emit("status-change",t,"waiting_input",{actionType:"idle",actionLabel:"\u76D1\u542C\u6D88\u606F",actionDetail:"aws-mcp/poll_message",usage:e.adapterSession.totalUsage}):this.emit("status-change",t,"waiting_input",{usage:e.adapterSession.totalUsage}),this.startIdleDetection(t);break}case"permission.updated":{const s=o.properties;if(s.sessionID!==i)break;const n=s.id;e.pendingPermissions.set(n,s),e.config.autoAccept&&e.client&&i?e.client.postSessionIdPermissionsPermissionId({path:{id:i,permissionID:n},body:{response:"once"},query:{directory:e.workingDirectory}}).then(()=>{e.pendingPermissions.delete(n)}).catch(a=>{console.warn("[OpencodeSdkAdapter] Auto-accept permission failed:",a)}):this.emitEvent({type:"permission_request",sessionId:t,timestamp:r,data:{permissionPrompt:s.title,toolName:s.type}});break}case"session.error":{const{sessionID:s,error:n}=o.properties;if(s&&s!==i)break;let a="OpenCode session error";if(n){const u=n.data;typeof u?.message=="string"?a=`${n.name}: ${u.message}`:a=n.name}this.emitEvent({type:"error",sessionId:t,timestamp:r,data:{text:a}});break}}}handlePartUpdate(t,e,o,r,i){switch(o.type){case"text":{let s;if(r!==void 0)s=r,e.seenTextPartIds.add(o.id);else if(!e.seenTextPartIds.has(o.id))s=o.text,s&&e.seenTextPartIds.add(o.id);else break;if(!s)break;e.currentAssistantText+=s,this.emitEvent({type:"text_delta",sessionId:t,timestamp:i,data:{text:s}});break}case"reasoning":{const s=r??o.text;if(!s)break;this.emitEvent({type:"thinking",sessionId:t,timestamp:i,data:{text:s}});break}case"tool":{const s=o.callID,n=o.tool,a=o.state,u=a.input,p=o.metadata;p&&Object.keys(p).length>0&&console.log(`[OpencodeSdkAdapter] Tool "${n}" metadata:`,JSON.stringify(I(p)));const c=I(u),S=c&&typeof c=="object"&&!Array.isArray(c)?c:{},g=()=>{if(e.emittedToolStarts.has(s))return;e.emittedToolStarts.add(s);const m={...P(n,u,p),actionId:s};this.emitEvent({type:"tool_use_start",sessionId:t,timestamp:i,data:{toolName:n,toolInput:S,toolUseId:s,...m}}),e.adapterSession.messages.push({id:k(),sessionId:t,role:"tool_use",content:`${n}: ${JSON.stringify(S).slice(0,100)}`,timestamp:i,toolName:n,toolInput:S,toolUseId:s})};if(a.status==="running"){g();const m={...P(n,u,p),actionId:s};this.emit("status-change",t,"tool_using",{...m,usage:e.adapterSession.totalUsage})}else if(a.status==="completed"){(n==="todowrite"||n==="todo_write")&&this.syncTodosFromToolCall(e,u).catch(w=>{console.warn("[OpencodeSdkAdapter] \u540C\u6B65 todo \u5230\u6587\u4EF6\u5931\u8D25:",w)}),g();const m=C(a.output);this.emitEvent({type:"tool_use_end",sessionId:t,timestamp:i,data:{toolName:n,toolResult:m,isError:!1,toolUseId:s}}),e.emittedToolStarts.delete(s)}else if(a.status==="error"){g();const m=C(a.error);this.emitEvent({type:"tool_use_end",sessionId:t,timestamp:i,data:{toolName:n,toolResult:m,isError:!0,toolUseId:s}}),e.emittedToolStarts.delete(s)}break}}}getTodoStoreDir(){return h.join(O.homedir(),".acode","todos")}async syncTodosFromToolCall(t,e){const o=t.config.agentId;if(!o)return;const r=e.todos;if(!Array.isArray(r)||r.length===0)return;const i=[];for(const c of r)if(c&&typeof c=="object"){const S=typeof c.content=="string"?c.content.trim():"",g=typeof c.status=="string"?c.status:"",m=typeof c.priority=="string"?c.priority:"";S&&g&&m&&i.push({content:S,status:g,priority:m})}if(i.length===0)return;const s=String(o).replace(/[^a-zA-Z0-9_-]/g,"_"),n=this.getTodoStoreDir(),a=h.join(n,`${s}.json`),u={agentId:o,updatedAt:new Date().toISOString(),background:"OpenCode runtime todos",todos:i};await y.promises.mkdir(n,{recursive:!0});const p=`${a}.tmp`;await y.promises.writeFile(p,JSON.stringify(u,null,2),"utf-8"),await y.promises.rename(p,a)}emitEvent(t){this.emit("event",t)}startIdleDetection(t){this.stopIdleDetection(t);const e=this.sessions.get(t),o=e?.idleCommands?.idleInputCommand;if(!e||!o)return;if(this.isMessagePollingIdleCommand(o)){e.idlePollLoop||(e.idlePollLoop=new D({getStatus:()=>this.sessions.get(t)?.adapterSession.status,getCommand:()=>this.sessions.get(t)?.idleCommands?.idleInputCommand,isPollingCommand:i=>this.isMessagePollingIdleCommand(i),dispatchPollingPrompt:i=>this.sendMessage(t,this.toIdlePrompt(i),{keepWaitingStatus:!0}),dispatchRegularIdleCommand:i=>this.sendMessage(t,this.toIdlePrompt(i)),pollDelayMs:1500})),e.idlePollLoop.start();return}e.idlePollLoop||(e.idlePollLoop=new D({getStatus:()=>this.sessions.get(t)?.adapterSession.status,getCommand:()=>this.sessions.get(t)?.idleCommands?.idleInputCommand,isPollingCommand:i=>this.isMessagePollingIdleCommand(i),dispatchPollingPrompt:i=>this.sendMessage(t,this.toIdlePrompt(i),{keepWaitingStatus:!0}),dispatchRegularIdleCommand:i=>this.sendMessage(t,this.toIdlePrompt(i)),pollDelayMs:1500}));const r=setInterval(()=>{this.checkIdleState(t)},500);e.idleTimer=r}stopIdleDetection(t){const e=this.sessions.get(t);e?.idlePollLoop?.stop(),e?.idleTimer&&(clearInterval(e.idleTimer),e.idleTimer=void 0)}checkIdleState(t){const e=this.sessions.get(t);!e||e.adapterSession.status!=="waiting_input"||e.idleCommands?.idleInputCommand&&(this.sendMessage(t,this.toIdlePrompt(e.idleCommands.idleInputCommand)),this.stopIdleDetection(t))}toIdlePrompt(t){const e=t.trim();return this.isMessagePollingIdleCommand(e)?"\u7CFB\u7EDF\u63D0\u9192\uFF1A\u5F53\u524D\u5DE5\u4F5C\u5DF2\u5B8C\u6210\u6216\u8FD0\u884C\u65F6\u5DF2\u8FDB\u5165\u7A7A\u95F2\u72B6\u6001\u3002\u8BF7\u7ACB\u5373\u8C03\u7528 poll_message \u5DE5\u5177\u963B\u585E\u7B49\u5F85\u65B0\u6D88\u606F\u548C\u6D41\u7A0B\u7EBF\u4EFB\u52A1\u63D0\u9192\uFF1B\u6536\u5230\u6D41\u7A0B\u4EFB\u52A1\u63D0\u9192\u540E\u8C03\u7528 my_task \u67E5\u770B\u5DF2\u7531\u540E\u53F0\u9759\u9ED8\u62A2\u5360\u7684\u4EFB\u52A1\u8BE6\u60C5\u5E76\u76F4\u63A5\u5904\u7406\uFF1B\u5B8C\u6210\u4EFB\u52A1\u540E\u8C03\u7528 submit_task_result \u63D0\u4EA4\u7ED3\u679C\uFF0C\u7136\u540E\u7EE7\u7EED poll_message\u3002 \u8C03\u7528 submit_task_result \u65F6\u8BF7\u4E00\u5E76\u4F20\u5165 changeLog\uFF08\u672C\u6B21\u5904\u7406\u7684\u6587\u4EF6/\u5185\u5BB9\u53D8\u66F4\u65E5\u5FD7\uFF0C\u5305\u542B operation \u4E0E path\uFF09\uFF0C\u7528\u4E8E AI \u56DE\u9000\u3002":e}isMessagePollingIdleCommand(t){const e=t.trim();if(!e)return!1;const o=e.toLowerCase();return o.includes("poll_message")||o.includes("get_message")||e.includes("\u53D6\u6D88\u606F")}}export{W as OpencodeSdkAdapter};
3
+ `).trim();return r?`OpenCode server exited during startup (${o}): ${r}`:`OpenCode server exited during startup (${o}). Please check whether opencode serve can run in this workspace.`}dispatchInitialPrompt(t,e){this.sendMessage(t,e).catch(o=>{const r=o instanceof Error?o.message:String(o);this.emitEvent({type:"error",sessionId:t,timestamp:new Date().toISOString(),data:{text:`OpenCode initial prompt failed: ${r}`}})})}startSseLoop(t,e){e.sseActive=!0;const o=new AbortController;e.sseAbortController=o,this.runSseLoop(t,e,o).catch(r=>{o.signal.aborted||console.error("[OpencodeSdkAdapter] SSE loop error:",r)})}async runSseLoop(t,e,o){if(e.client)try{const r=await e.client.event.subscribe({query:{directory:e.workingDirectory},signal:o.signal});for await(const i of r.stream){if(!e.sseActive||o.signal.aborted)break;this.handleOpenCodeEvent(t,e,i)}}catch(r){if(o.signal.aborted)return;console.error("[OpencodeSdkAdapter] SSE stream ended:",r)}}handleOpenCodeEvent(t,e,o){const r=new Date().toISOString(),i=e.opencodeSessionId;switch(o.type){case"message.updated":{const s=o.properties.info;if(s.sessionID!==i)break;if(s.role==="user")e.userMessageIds.add(s.id);else if(s.role==="assistant"){const n=s.tokens;n&&(e.adapterSession.totalUsage.inputTokens=Math.max(e.adapterSession.totalUsage.inputTokens,n.input),e.adapterSession.totalUsage.outputTokens=Math.max(e.adapterSession.totalUsage.outputTokens,n.output))}break}case"message.part.updated":{const{part:s,delta:n}=o.properties;if(s.sessionID!==i||e.userMessageIds.has(s.messageID))break;this.handlePartUpdate(t,e,s,n,r);break}case"session.idle":{if(o.properties.sessionID!==i)break;if(e.currentAssistantText){const a={id:y(),sessionId:t,role:"assistant",content:e.currentAssistantText,timestamp:r};e.adapterSession.messages.push(a),e.currentAssistantText=""}e.seenTextPartIds.clear(),this.emitEvent({type:"turn_complete",sessionId:t,timestamp:r,data:{usage:e.adapterSession.totalUsage}}),e.adapterSession.status="waiting_input";const s=e.idleCommands?.idleInputCommand;!!(s&&this.isMessagePollingIdleCommand(s))?this.emit("status-change",t,"waiting_input",{actionType:"idle",actionLabel:"\u76D1\u542C\u6D88\u606F",actionDetail:"aws-mcp/poll_message",usage:e.adapterSession.totalUsage}):this.emit("status-change",t,"waiting_input",{usage:e.adapterSession.totalUsage}),this.startIdleDetection(t);break}case"permission.updated":{const s=o.properties;if(s.sessionID!==i)break;const n=s.id;e.pendingPermissions.set(n,s),e.config.autoAccept&&e.client&&i?e.client.postSessionIdPermissionsPermissionId({path:{id:i,permissionID:n},body:{response:"once"},query:{directory:e.workingDirectory}}).then(()=>{e.pendingPermissions.delete(n)}).catch(a=>{console.warn("[OpencodeSdkAdapter] Auto-accept permission failed:",a)}):this.emitEvent({type:"permission_request",sessionId:t,timestamp:r,data:{permissionPrompt:s.title,toolName:s.type}});break}case"session.error":{const{sessionID:s,error:n}=o.properties;if(s&&s!==i)break;let a="OpenCode session error";if(n){const u=n.data;typeof u?.message=="string"?a=`${n.name}: ${u.message}`:a=n.name}this.emitEvent({type:"error",sessionId:t,timestamp:r,data:{text:a}});break}}}handlePartUpdate(t,e,o,r,i){switch(o.type){case"text":{let s;if(r!==void 0)s=r,e.seenTextPartIds.add(o.id);else if(!e.seenTextPartIds.has(o.id))s=o.text,s&&e.seenTextPartIds.add(o.id);else break;if(!s)break;e.currentAssistantText+=s,this.emitEvent({type:"text_delta",sessionId:t,timestamp:i,data:{text:s}});break}case"reasoning":{const s=r??o.text;if(!s)break;this.emitEvent({type:"thinking",sessionId:t,timestamp:i,data:{text:s}});break}case"tool":{const s=o.callID,n=o.tool,a=o.state,u=a.input,l=o.metadata;l&&Object.keys(l).length>0&&console.log(`[OpencodeSdkAdapter] Tool "${n}" metadata:`,JSON.stringify(I(l)));const p=I(u),S=p&&typeof p=="object"&&!Array.isArray(p)?p:{},g=()=>{if(e.emittedToolStarts.has(s))return;e.emittedToolStarts.add(s);const m={...P(n,u,l),actionId:s};this.emitEvent({type:"tool_use_start",sessionId:t,timestamp:i,data:{toolName:n,toolInput:S,toolUseId:s,...m}}),e.adapterSession.messages.push({id:y(),sessionId:t,role:"tool_use",content:`${n}: ${JSON.stringify(S).slice(0,100)}`,timestamp:i,toolName:n,toolInput:S,toolUseId:s})};if(a.status==="running"){g();const m={...P(n,u,l),actionId:s};this.emit("status-change",t,"tool_using",{...m,usage:e.adapterSession.totalUsage})}else if(a.status==="completed"){(n==="todowrite"||n==="todo_write")&&this.syncTodosFromToolCall(e,u).catch(k=>{console.warn("[OpencodeSdkAdapter] \u540C\u6B65 todo \u5230\u6587\u4EF6\u5931\u8D25:",k)}),g();const m=C(a.output);this.emitEvent({type:"tool_use_end",sessionId:t,timestamp:i,data:{toolName:n,toolResult:m,isError:!1,toolUseId:s}}),e.emittedToolStarts.delete(s)}else if(a.status==="error"){g();const m=C(a.error);this.emitEvent({type:"tool_use_end",sessionId:t,timestamp:i,data:{toolName:n,toolResult:m,isError:!0,toolUseId:s}}),e.emittedToolStarts.delete(s)}break}}}getTodoStoreDir(){return h.join(O.homedir(),".acode","todos")}async syncTodosFromToolCall(t,e){const o=t.config.agentId;if(!o)return;const r=e.todos;if(!Array.isArray(r)||r.length===0)return;const i=[];for(const p of r)if(p&&typeof p=="object"){const S=typeof p.content=="string"?p.content.trim():"",g=typeof p.status=="string"?p.status:"",m=typeof p.priority=="string"?p.priority:"";S&&g&&m&&i.push({content:S,status:g,priority:m})}if(i.length===0)return;const s=String(o).replace(/[^a-zA-Z0-9_-]/g,"_"),n=this.getTodoStoreDir(),a=h.join(n,`${s}.json`),u={agentId:o,updatedAt:new Date().toISOString(),background:"OpenCode runtime todos",todos:i};await w.promises.mkdir(n,{recursive:!0});const l=`${a}.tmp`;await w.promises.writeFile(l,JSON.stringify(u,null,2),"utf-8"),await w.promises.rename(l,a)}emitEvent(t){this.emit("event",t)}startIdleDetection(t){this.stopIdleDetection(t);const e=this.sessions.get(t),o=e?.idleCommands?.idleInputCommand;if(!e||!o)return;if(this.isMessagePollingIdleCommand(o)){e.idlePollLoop||(e.idlePollLoop=new D({getStatus:()=>this.sessions.get(t)?.adapterSession.status,getCommand:()=>this.sessions.get(t)?.idleCommands?.idleInputCommand,isPollingCommand:i=>this.isMessagePollingIdleCommand(i),preDispatchHook:()=>null,dispatchPollingPrompt:(i,s)=>this.sendMessage(t,s?i.trim():this.toIdlePrompt(i),{keepWaitingStatus:!0}),dispatchRegularIdleCommand:i=>this.sendMessage(t,this.toIdlePrompt(i)),pollDelayMs:1500})),e.idlePollLoop.start();return}e.idlePollLoop||(e.idlePollLoop=new D({getStatus:()=>this.sessions.get(t)?.adapterSession.status,getCommand:()=>this.sessions.get(t)?.idleCommands?.idleInputCommand,isPollingCommand:i=>this.isMessagePollingIdleCommand(i),preDispatchHook:()=>null,dispatchPollingPrompt:(i,s)=>this.sendMessage(t,s?i.trim():this.toIdlePrompt(i),{keepWaitingStatus:!0}),dispatchRegularIdleCommand:i=>this.sendMessage(t,this.toIdlePrompt(i)),pollDelayMs:1500}));const r=setInterval(()=>{this.checkIdleState(t)},500);e.idleTimer=r}stopIdleDetection(t){const e=this.sessions.get(t);e?.idlePollLoop?.stop(),e?.idleTimer&&(clearInterval(e.idleTimer),e.idleTimer=void 0)}checkIdleState(t){const e=this.sessions.get(t);!e||e.adapterSession.status!=="waiting_input"||e.idleCommands?.idleInputCommand&&(this.sendMessage(t,this.toIdlePrompt(e.idleCommands.idleInputCommand)),this.stopIdleDetection(t))}toIdlePrompt(t){const e=t.trim();return this.isMessagePollingIdleCommand(e)?"\u7CFB\u7EDF\u63D0\u9192\uFF1A\u5982\u679C\u4EFB\u52A1\u5DF2\u5B8C\u6210\u6216\u6CA1\u6709\u5176\u5B83\u8981\u505A\u7684\uFF0C\u5148\u8C03\u7528 poll_message \u5DE5\u5177\u963B\u585E\u7B49\u5F85\u65B0\u6D88\u606F\u548C\u6D41\u7A0B\u7EBF\u4EFB\u52A1\u63D0\u9192\uFF1B\u6536\u5230\u6D41\u7A0B\u4EFB\u52A1\u63D0\u9192\u540E\u8C03\u7528 my_task \u67E5\u770B\u5DF2\u7531\u540E\u53F0\u9759\u9ED8\u62A2\u5360\u7684\u4EFB\u52A1\u8BE6\u60C5\u5E76\u76F4\u63A5\u5904\u7406\uFF1B\u5B8C\u6210\u4EFB\u52A1\u540E\u8C03\u7528 submit_task_result \u63D0\u4EA4\u7ED3\u679C\uFF0C\u7136\u540E\u7EE7\u7EED poll_message\u3002 \u8C03\u7528 submit_task_result \u65F6\u8BF7\u4E00\u5E76\u4F20\u5165 changeLog\uFF08\u672C\u6B21\u5904\u7406\u7684\u6587\u4EF6/\u5185\u5BB9\u53D8\u66F4\u65E5\u5FD7\uFF0C\u5305\u542B operation \u4E0E path\uFF09\uFF0C\u7528\u4E8E AI \u56DE\u9000\u3002":e}isMessagePollingIdleCommand(t){const e=t.trim();if(!e)return!1;const o=e.toLowerCase();return o.includes("poll_message")||o.includes("get_message")||e.includes("\u53D6\u6D88\u606F")}}export{B as OpencodeSdkAdapter};
4
4
 
@@ -4,8 +4,11 @@ export interface IdlePollLoopOptions {
4
4
  readonly getStatus: () => SessionStatus | undefined;
5
5
  readonly getCommand: () => string | undefined;
6
6
  readonly isPollingCommand: (command: string) => boolean;
7
- readonly dispatchPollingPrompt: (command: string) => void | Promise<void>;
7
+
8
+ readonly dispatchPollingPrompt: (command: string, injected?: boolean) => void | Promise<void>;
8
9
  readonly dispatchRegularIdleCommand: (command: string) => void | Promise<void>;
10
+
11
+ readonly preDispatchHook?: () => string | null | Promise<string | null>;
9
12
  readonly pollDelayMs?: number;
10
13
  readonly regularIdleDelayMs?: number;
11
14
  }
@@ -1,2 +1,2 @@
1
- class e{constructor(i){this.options=i,this.running=!1,this.pollDelayMs=i.pollDelayMs??0,this.regularIdleDelayMs=i.regularIdleDelayMs??1500}start(){this.stop();const i=this.options.getCommand()?.trim();if(!i||this.options.getStatus()!=="waiting_input")return;const t=this.options.isPollingCommand(i)?this.pollDelayMs:this.regularIdleDelayMs;if(t<=0){this.fire(i);return}this.timer=setTimeout(()=>{this.timer=void 0,this.fire(i)},t)}stop(){this.timer&&(clearTimeout(this.timer),this.timer=void 0)}async fire(i){if(!(this.running||this.options.getStatus()!=="waiting_input")){this.running=!0;try{this.options.isPollingCommand(i)?await this.options.dispatchPollingPrompt(i):await this.options.dispatchRegularIdleCommand(i)}finally{this.running=!1}}}}export{e as IdlePollLoop};
1
+ class e{constructor(t){this.options=t,this.running=!1,this.pollDelayMs=t.pollDelayMs??0,this.regularIdleDelayMs=t.regularIdleDelayMs??1500}start(){this.stop();const t=this.options.getCommand()?.trim();if(!t||this.options.getStatus()!=="waiting_input")return;const i=this.options.isPollingCommand(t)?this.pollDelayMs:this.regularIdleDelayMs;if(i<=0){this.fire(t);return}this.timer=setTimeout(()=>{this.timer=void 0,this.fire(t)},i)}stop(){this.timer&&(clearTimeout(this.timer),this.timer=void 0)}async fire(t){if(!(this.running||this.options.getStatus()!=="waiting_input")){this.running=!0;try{if(this.options.isPollingCommand(t)){const i=this.options.preDispatchHook?await this.options.preDispatchHook():null;i?await this.options.dispatchPollingPrompt(i,!0):await this.options.dispatchPollingPrompt(t,!1)}else await this.options.dispatchRegularIdleCommand(t)}finally{this.running=!1}}}}export{e as IdlePollLoop};
2
2
 
@@ -1,9 +1,9 @@
1
1
 
2
2
  import { EventEmitter } from "node:events";
3
3
  export type SessionStatus = "starting" | "thinking" | "tool_using" | "waiting_input" | "completed" | "terminated" | "error" | "compacting";
4
- export type ProviderEventType = "ai_request" | "text_delta" | "thinking" | "command_output" | "tool_use_start" | "tool_use_end" | "permission_request" | "ask_user_question" | "exit_plan_mode" | "turn_complete" | "session_complete" | "error" | "context_compaction" | "runtime_capabilities";
4
+ export type ProviderEventType = "ai_request" | "text_delta" | "thinking" | "command_output" | "tool_use_start" | "tool_use_end" | "permission_request" | "ask_user_question" | "exit_plan_mode" | "turn_complete" | "session_complete" | "error" | "context_compaction" | "runtime_capabilities" | "background_command_completed";
5
5
 
6
- export type ToolActionType = "skill" | "mcp" | "idle" | "edit_file" | "write_file" | "read_file" | "bash" | "search" | "web" | "ai_request" | "lsp" | "notebook" | "other" | "context_compaction" | "runtime_capabilities";
6
+ export type ToolActionType = "skill" | "mcp" | "idle" | "edit_file" | "write_file" | "read_file" | "bash" | "search" | "web" | "ai_request" | "lsp" | "notebook" | "other" | "context_compaction" | "runtime_capabilities" | "background_command";
7
7
 
8
8
  export interface ToolActionInfo {
9
9
 
@@ -190,6 +190,8 @@ export interface AdapterSessionConfig {
190
190
 
191
191
  agentId?: string;
192
192
 
193
+ scopeKey?: string;
194
+
193
195
  initialPrompt?: string;
194
196
 
195
197
  initialPromptVisibility?: "visible" | "hidden";
@@ -0,0 +1,73 @@
1
+
2
+ import type { Page } from "playwright";
3
+ import { type BrowserPoolConfig, type SessionBrowser } from "./types.js";
4
+ import type { ListenerTriggerEvent, PageListener } from "./page-listener-types.js";
5
+
6
+ export interface SessionSummary {
7
+ agentId: string;
8
+ browserId: string;
9
+
10
+ title: string;
11
+ pageCount: number;
12
+ activePageIndex: number;
13
+ launchedAt: number;
14
+ lastActivityAt: number;
15
+ currentUrl: string;
16
+ currentTitle: string;
17
+ }
18
+
19
+ export declare class BrowserPool {
20
+ private readonly config;
21
+ private readonly sessions;
22
+ private idleTimer;
23
+
24
+ private readonly sessionCloseHandlers;
25
+
26
+ onSessionClose(handler: (agentId: string, browserId: string) => void): void;
27
+
28
+ private readonly profileStore;
29
+
30
+ private readonly listenerStore;
31
+
32
+ private listenerTriggerHook;
33
+
34
+ setListenerTriggerHook(hook: ((agentId: string, browserId: string, event: ListenerTriggerEvent, listener: PageListener) => void) | null): void;
35
+
36
+ private resolveKey;
37
+ constructor(config?: BrowserPoolConfig);
38
+
39
+ launch(agentId: string, browserId?: string, title?: string, scopeKey?: string): Promise<SessionBrowser>;
40
+
41
+ get(agentId: string, browserId?: string): SessionBrowser | undefined;
42
+
43
+ getOrLaunch(agentId: string, browserId?: string, title?: string, scopeKey?: string): Promise<SessionBrowser>;
44
+
45
+ close(agentId: string, browserId?: string): Promise<void>;
46
+
47
+ closeByAgent(agentId: string): Promise<void>;
48
+
49
+ collectListenerEvents(agentId: string): Promise<ListenerTriggerEvent[]>;
50
+
51
+ getActivePage(agentId: string, browserId?: string): Page;
52
+
53
+ setActivePage(agentId: string, index: number, browserId?: string): Promise<Page>;
54
+
55
+ newPage(agentId: string, browserId?: string): Promise<Page>;
56
+
57
+ closePage(agentId: string, index: number, browserId?: string): Promise<void>;
58
+
59
+ listSessions(agentId?: string): SessionSummary[];
60
+
61
+ touchActivity(agentId: string, browserId?: string): void;
62
+
63
+ dispose(): Promise<void>;
64
+ private requireSession;
65
+ private ensureIdleTimer;
66
+ private stopIdleTimer;
67
+
68
+ private checkIdleSessions;
69
+ }
70
+
71
+ export declare function getBrowserPool(config?: BrowserPoolConfig): BrowserPool;
72
+
73
+ export declare function disposeBrowserPool(): Promise<void>;
@@ -0,0 +1,2 @@
1
+ import{chromium as M}from"playwright";import{DEFAULT_BROWSER_ID as h,IDLE_CHECK_INTERVAL_MS as _,IDLE_TIMEOUT_MS as C,MAX_PAGES_PER_SESSION as D,MAX_SESSIONS as x,MAX_SESSIONS_PER_AGENT as E,SESSION_TIMEOUT_MS as $,makeBrowserKey as k}from"./types.js";import{ScreencastManager as H}from"./screencast-manager.js";import{StepArchive as b}from"./step-archive.js";import{BrowserProfileStore as O}from"./browser-profile-store.js";import{ListenerStore as L}from"./listener-store.js";import{PageListenerManager as N}from"./page-listener-manager.js";import{buildStealthInitScript as B,STEALTH_LAUNCH_ARGS as R,STEALTH_IGNORE_ARGS as K}from"./stealth.js";const U=1280,z=800;class G{onSessionClose(e){this.sessionCloseHandlers.push(e)}setListenerTriggerHook(e){this.listenerTriggerHook=e}resolveKey(e,t){if(!e||typeof e!="string")throw new Error("scopeKey must be a non-empty string");const i=t&&t.trim()?t.trim():h;if(i.includes("::"))throw new Error(`browserId must not contain "::" (reserved separator): ${i}`);return k(e,i)}constructor(e){this.sessions=new Map,this.idleTimer=null,this.sessionCloseHandlers=[],this.listenerTriggerHook=null,this.config={headless:e?.headless??!0,executablePath:e?.executablePath,viewportWidth:e?.viewportWidth??U,viewportHeight:e?.viewportHeight??z,maxPagesPerSession:e?.maxPagesPerSession??D,maxSessionDurationMs:e?.maxSessionDurationMs??$,idleTimeoutMs:e?.idleTimeoutMs??C,launchArgs:e?.launchArgs,screencastConfig:e?.screencastConfig,archiveConfig:e?.archiveConfig,ssrfConfig:e?.ssrfConfig,userAgent:e?.userAgent,locale:e?.locale,timezone:e?.timezone,profileDir:e?.profileDir,stealth:e?.stealth??!0},this.profileStore=e?.profileDir?new O({profileDir:e.profileDir}):null,this.listenerStore=e?.profileDir?new L({listenerDir:e.profileDir}):null}async launch(e,t,i,s){const r=s??`agent:${e}`,n=this.resolveKey(r,t);if(this.sessions.has(n))throw new Error(`Browser session already exists for ${e}/${t??h}`);if(this.sessions.size>=x)throw new Error(`BrowserPool reached MAX_SESSIONS=${x}; close existing sessions before launching new ones`);let c=0;for(const o of this.sessions.values())o.agentId===e&&(c+=1);if(c>=E)throw new Error(`BrowserPool reached MAX_SESSIONS_PER_AGENT=${E} for agent ${e}; close some browser sessions first`);const l={headless:this.config.headless};this.config.executablePath&&(l.executablePath=this.config.executablePath),this.config.stealth?(l.ignoreDefaultArgs=[...K],l.args=[...R,...this.config.launchArgs??[]]):this.config.launchArgs&&this.config.launchArgs.length>0&&(l.args=this.config.launchArgs);const p=await M.launch(l);let m=this.config.userAgent,y;if(this.config.stealth)try{const o=await p.newBrowserCDPSession(),f=await o.send("Browser.getVersion"),g=f.product?.match(/Chrome\/(\d+)/);g&&(y=g[1]),!m&&f.userAgent&&(m=String(f.userAgent).replace(/HeadlessChrome\/([\d.]+)/g,"Chrome/$1")),await o.detach().catch(()=>{})}catch{}const w={viewport:{width:this.config.viewportWidth,height:this.config.viewportHeight}};if(m&&(w.userAgent=m),this.config.locale&&(w.locale=this.config.locale),this.config.timezone&&(w.timezoneId=this.config.timezone),this.profileStore)try{let o=await this.profileStore.load(r);!o&&r!==e&&(o=await this.profileStore.load(e),o&&await this.profileStore.save(r,o)),o&&(w.storageState=o)}catch{}const v=await p.newContext(w).catch(async o=>{throw await p.close().catch(()=>{}),o});this.config.stealth&&await v.addInitScript(B(y??"126")).catch(()=>{});const S=await v.newPage().catch(async o=>{throw await p.close().catch(()=>{}),o}),P=new H(this.config.screencastConfig),I=new b(this.config.archiveConfig),A=new N(e,t?.trim()||h,{onTrigger:(o,f)=>{const g=this.listenerTriggerHook;if(g)try{g(e,t?.trim()||h,o,f)}catch{}},store:this.listenerStore,scopeKey:r});try{await P.start(S)}catch{}try{const o=S;o.screencast?.showActions&&await o.screencast.showActions({cursor:"pointer"})}catch{}const T=Date.now(),a={agentId:e,scopeKey:r,browserId:t?.trim()||h,title:i?.trim()||"\u6D4F\u89C8\u5668\u4F1A\u8BDD",browser:p,context:v,pages:[S],activePageIndex:0,screencast:P,archive:I,listenerManager:A,launchedAt:T,lastActivityAt:T,humanTakeover:!1,qualityPreset:"original"};v.on("page",o=>{if(a.pages.length>=this.config.maxPagesPerSession){o.close().catch(()=>{});return}a.pages.push(o);const f=a.pages.length-1;this.setActivePage(e,f,a.browserId).catch(()=>{}),o.on("close",()=>{const g=a.pages.indexOf(o);if(g!==-1){if(a.pages.splice(g,1),a.pages.length===0){this.close(e,a.browserId).catch(()=>{});return}a.activePageIndex>=a.pages.length?a.activePageIndex=a.pages.length-1:g<a.activePageIndex&&(a.activePageIndex-=1)}})});try{await A.attach(S)}catch{}return this.sessions.set(n,a),this.ensureIdleTimer(),this.profileStore&&(a.autoSaveTimer=setInterval(async()=>{try{const o=await a.context.storageState();await this.profileStore.save(a.scopeKey,o)}catch{}},3e4),a.autoSaveTimer.unref&&a.autoSaveTimer.unref()),a}get(e,t){const i=t&&t.trim()?t.trim():h;for(const s of this.sessions.values())if(s.agentId===e&&s.browserId===i)return s}async getOrLaunch(e,t,i,s){const r=s??`agent:${e}`,n=this.resolveKey(r,t),c=this.sessions.get(n);return c||this.launch(e,t,i,s)}async close(e,t){const i=t&&t.trim()?t.trim():h;let s,r;for(const[c,l]of this.sessions)if(l.agentId===e&&l.browserId===i){s=c,r=l;break}if(!s||!r)return;const n=r;n.autoSaveTimer&&(clearInterval(n.autoSaveTimer),n.autoSaveTimer=void 0);try{await n.screencast.dispose()}catch{}try{await n.listenerManager.dispose()}catch{}if(n.archive.clear(),this.profileStore)try{const c=await n.context.storageState();await this.profileStore.save(n.scopeKey,c)}catch{}try{await n.browser.close()}catch{}this.sessions.delete(s);for(const c of this.sessionCloseHandlers)try{c(e,i)}catch{}this.sessions.size===0&&this.stopIdleTimer()}async closeByAgent(e){if(!e)return;const t=[];for(const[i,s]of this.sessions)s.agentId===e&&t.push(i);await Promise.all(t.map(i=>{const r=this.sessions.get(i)?.browserId;return this.close(e,r)}))}async collectListenerEvents(e){if(!e)return[];const t=[];for(const i of this.sessions.values()){if(i.agentId!==e)continue;await i.listenerManager.scanExistsAtPoll();const s=i.listenerManager.collectEvents();s.length>0&&t.push(...s)}return t.sort((i,s)=>i.triggeredAt-s.triggeredAt),t}getActivePage(e,t){const i=this.requireSession(e,t),s=i.pages[i.activePageIndex];if(!s||s.isClosed())throw new Error(`No active page for agent ${e}/${i.browserId} (index ${i.activePageIndex})`);return s}async setActivePage(e,t,i){const s=this.requireSession(e,i);if(!Number.isInteger(t)||t<0||t>=s.pages.length)throw new Error(`Page index ${t} out of range (0..${s.pages.length-1}) for agent ${e}/${s.browserId}`);if(s.activePageIndex===t)return s.pages[t];const r=s.pages[s.activePageIndex];if(r&&!r.isClosed())try{await s.screencast.stop(r)}catch{}s.activePageIndex=t;const n=s.pages[t];try{await s.screencast.start(n)}catch{}try{await s.listenerManager.reattach(n)}catch{}return this.touchActivity(e,i),n}async newPage(e,t){const i=this.requireSession(e,t);if(i.pages.length>=this.config.maxPagesPerSession)throw new Error(`Agent ${e}/${i.browserId} reached maxPagesPerSession=${this.config.maxPagesPerSession}; close a tab first`);const s=await i.context.newPage();i.pages.push(s);const r=i.pages.length-1;return this.touchActivity(e,t),this.setActivePage(e,r,t)}async closePage(e,t,i){const s=this.requireSession(e,i);if(!Number.isInteger(t)||t<0||t>=s.pages.length)throw new Error(`Page index ${t} out of range (0..${s.pages.length-1}) for agent ${e}/${s.browserId}`);const r=s.pages[t];if(t===s.activePageIndex)try{await s.screencast.stop(r)}catch{}try{await r.close()}catch{}if(s.pages.splice(t,1),s.pages.length===0){await this.close(e,i);return}s.activePageIndex>=s.pages.length?s.activePageIndex=s.pages.length-1:t<s.activePageIndex&&(s.activePageIndex-=1);const n=s.pages[s.activePageIndex];if(!n.isClosed()){try{await s.screencast.start(n)}catch{}try{await s.listenerManager.reattach(n)}catch{}}this.touchActivity(e,i)}listSessions(e){const t=[];for(const i of this.sessions.values()){if(e&&i.agentId!==e)continue;const s=i.pages[i.activePageIndex];let r="",n="";if(s&&!s.isClosed())try{r=s.url()}catch{}t.push({agentId:i.agentId,browserId:i.browserId,title:i.title,pageCount:i.pages.length,activePageIndex:i.activePageIndex,launchedAt:i.launchedAt,lastActivityAt:i.lastActivityAt,currentUrl:r,currentTitle:n})}return t}touchActivity(e,t){const i=t&&t.trim()?t.trim():h;for(const s of this.sessions.values())if(s.agentId===e&&s.browserId===i){s.lastActivityAt=Date.now();return}}async dispose(){this.stopIdleTimer();const e=Array.from(this.sessions.keys());await Promise.all(e.map(t=>{const i=this.sessions.get(t);return i?this.close(i.agentId,i.browserId):Promise.resolve()}))}requireSession(e,t){const i=t&&t.trim()?t.trim():h;for(const s of this.sessions.values())if(s.agentId===e&&s.browserId===i)return s;throw new Error(`Browser session not launched for ${e}/${t??h}`)}ensureIdleTimer(){this.idleTimer||this.config.idleTimeoutMs<=0&&this.config.maxSessionDurationMs<=0||(this.idleTimer=setInterval(()=>{this.checkIdleSessions()},_),this.idleTimer.unref&&this.idleTimer.unref())}stopIdleTimer(){this.idleTimer&&(clearInterval(this.idleTimer),this.idleTimer=null)}async checkIdleSessions(){const e=Date.now(),t=[];for(const i of this.sessions.values()){if(i.humanTakeover)continue;const s=e-i.lastActivityAt,r=e-i.launchedAt;if(this.config.idleTimeoutMs>0&&s>this.config.idleTimeoutMs){t.push(i);continue}this.config.maxSessionDurationMs>0&&r>this.config.maxSessionDurationMs&&t.push(i)}await Promise.all(t.map(i=>this.close(i.agentId,i.browserId)))}}let u=null;function Y(d){return u||(u=new G(d)),u}async function Z(){u&&(await u.dispose(),u=null)}export{G as BrowserPool,Z as disposeBrowserPool,Y as getBrowserPool};
2
+
@@ -0,0 +1,28 @@
1
+
2
+ import type { BrowserContext } from "playwright";
3
+
4
+ export type StorageState = Awaited<ReturnType<BrowserContext["storageState"]>>;
5
+
6
+ export interface BrowserProfileStoreConfig {
7
+
8
+ profileDir: string;
9
+ }
10
+
11
+ export declare class BrowserProfileStore {
12
+ private readonly config;
13
+
14
+ private readonly writeChains;
15
+ constructor(config: BrowserProfileStoreConfig);
16
+
17
+ private filePath;
18
+
19
+ private enqueueWrite;
20
+
21
+ load(scopeKey: string): Promise<StorageState | undefined>;
22
+
23
+ save(scopeKey: string, next: StorageState): Promise<void>;
24
+
25
+ clear(scopeKey: string): Promise<void>;
26
+ }
27
+
28
+ export declare function mergeStorageState(prev: StorageState | undefined, next: StorageState): StorageState;
@@ -0,0 +1,2 @@
1
+ import{mkdir as f,readFile as u,writeFile as l,rename as m,unlink as h}from"node:fs/promises";import{dirname as g,join as p}from"node:path";function w(s){const t=s.replace(/[^a-zA-Z0-9._-]/g,"_");return t===""||/^\.+$/.test(t)?"_":t}class k{constructor(t){this.config=t,this.writeChains=new Map}filePath(t){return p(this.config.profileDir,w(t),"storage-state.json")}enqueueWrite(t,n){const i=(this.writeChains.get(t)??Promise.resolve()).then(n);return this.writeChains.set(t,i.catch(()=>{})),i}async load(t){try{const n=await u(this.filePath(t),"utf-8"),r=JSON.parse(n);return!r||!Array.isArray(r.cookies)||!Array.isArray(r.origins)?void 0:r}catch{return}}async save(t,n){return this.enqueueWrite(t,async()=>{const r=await this.load(t),i=d(r,n),e=this.filePath(t);await f(g(e),{recursive:!0});const o=`${e}.tmp`;await l(o,JSON.stringify(i),"utf-8"),await m(o,e)})}async clear(t){return this.enqueueWrite(t,async()=>{try{await h(this.filePath(t))}catch{}})}}function d(s,t){if(!s)return t;const n=new Map,r=e=>`${e.name}|${e.domain}|${e.path}`;for(const e of s.cookies)n.set(r(e),e);for(const e of t.cookies)n.set(r(e),e);const i=new Map;for(const e of s.origins){const o=i.get(e.origin)??new Map;for(const a of e.localStorage)o.set(a.name,a.value);i.set(e.origin,o)}for(const e of t.origins){const o=i.get(e.origin)??new Map;for(const a of e.localStorage)o.set(a.name,a.value);i.set(e.origin,o)}return{cookies:Array.from(n.values()),origins:Array.from(i.entries()).map(([e,o])=>({origin:e,localStorage:Array.from(o.entries()).map(([a,c])=>({name:a,value:c}))}))}}export{k as BrowserProfileStore,d as mergeStorageState};
2
+
@@ -0,0 +1,27 @@
1
+
2
+ import type { JsonObject } from "./types.js";
3
+ import type { BrowserPool } from "./browser-pool.js";
4
+ import type { BrowserWsServer } from "./ws-server.js";
5
+
6
+ export interface BrowserToolExecutionContext {
7
+ readonly toolCallId?: string;
8
+ }
9
+
10
+ export interface ACodeBuiltInToolLike {
11
+ readonly kind: "builtin";
12
+ readonly toolName: string;
13
+ readonly exposedName: string;
14
+ readonly description: string;
15
+ readonly inputSchema: JsonObject;
16
+ execute(args: JsonObject, context?: BrowserToolExecutionContext): Promise<unknown>;
17
+ }
18
+
19
+ export interface CreateBuiltInBrowserToolsOptions {
20
+ pool: BrowserPool;
21
+ wsServer?: BrowserWsServer;
22
+ agentId?: string;
23
+
24
+ scopeKey?: string;
25
+ }
26
+
27
+ export declare function createBuiltInBrowserTools(options: CreateBuiltInBrowserToolsOptions): ACodeBuiltInToolLike[];
@@ -0,0 +1,2 @@
1
+ import{validateUrlWithDns as R}from"./ssrf-guard.js";import{locateElement as q}from"./element-locator.js";import{serializeSnapshot as P,buildRefMap as E}from"./snapshot-serializer.js";import{DEFAULT_BROWSER_ID as v}from"./types.js";const M=3e4,j=1e4;function te(k){const{pool:y,wsServer:_}=k,C=k.agentId,N=k.scopeKey;function u(e){const r=(typeof e.agentId=="string"&&e.agentId.trim()?e.agentId.trim():void 0)??C;if(!r)throw new Error("agentId is required: pass via createBuiltInBrowserTools({agentId}) or tool args.agentId");const n=typeof e.browserId=="string"&&e.browserId.trim()?e.browserId.trim():v,s=typeof e.title=="string"&&e.title.trim()?e.title.trim():void 0,o=N??`agent:${r}`;return{agentId:r,browserId:n,title:s,scopeKey:o}}function D(e,t){const r=y.get(e,t);if(!r)throw new Error(`Browser session not launched for ${e}/${t}. Call browser_launch first.`);if(r.humanTakeover)throw new Error(`Human takeover active for ${e}/${t}. Release takeover before calling browser tools.`);return y.touchActivity(e,t),r}function m(e,t){return{session:D(e,t),page:y.getActivePage(e,t)}}function p(e){return{ok:!1,error:e instanceof Error?e.message:String(e)}}function I(e,t){if(e)try{e.archive.record(t)}catch{}}function x(e){const t={};for(const[r,n]of Object.entries(e))n!==void 0&&(t[r]=n);return t}const T=new Map;function A(e,t){return`${e}:${t}`}function U(e,t,r){return T.get(A(e,t))?.get(r)}function B(e,t,r){T.set(A(e,t),E(r))}return k.pool.onSessionClose((e,t)=>{T.delete(A(e,t))}),[{kind:"builtin",toolName:"browser_launch",exposedName:"browser_launch",description:"Launch a headless browser session for the current agent. Returns wsUrl for live JPEG frame streaming (open in browser to watch the agent operate). Pass browserId to start a separate browser instance (independent cookies/cache/storage). Default browserId is 'default'. Must be called before any other browser_* tool. The session is automatically reclaimed after 10 minutes of inactivity. title is required and should briefly describe the intent of this browser session (e.g. '\u641C\u7D22\u7ADE\u54C1\u4EF7\u683C', '\u767B\u5F55\u7BA1\u7406\u540E\u53F0').",inputSchema:{type:"object",properties:{agentId:{type:"string",description:"Agent identifier (optional if configured at factory time). Used to isolate browser sessions across agents."},browserId:{type:"string",description:"Browser instance ID (optional, default 'default'). Same agent can have multiple browserIds (each = independent chromium process)."},title:{type:"string",description:"Brief human-readable title describing the intent of this browser session (e.g. '\u641C\u7D22\u7ADE\u54C1\u4EF7\u683C', '\u767B\u5F55\u7BA1\u7406\u540E\u53F0'). Required \u2014 used as tab label in dashboard."}},required:["title"]},async execute(e){try{const{agentId:t,browserId:r,title:n,scopeKey:s}=u(e),o=await y.getOrLaunch(t,r,n,s),a=o.pages[o.activePageIndex].viewportSize(),c={ok:!0,agentId:t,browserId:o.browserId,title:o.title,viewport:{width:a?.width??0,height:a?.height??0}};return _&&(c.wsUrl=_.getUrl(t,o.browserId)),c}catch(t){return p(t)}}},{kind:"builtin",toolName:"browser_navigate",exposedName:"browser_navigate",description:"Navigate the active page to a URL. Waits for DOMContentLoaded (30s timeout). SSRF guard rejects private/loopback addresses by default.",inputSchema:{type:"object",properties:{url:{type:"string",description:"Target URL (http/https only). Private IP ranges are blocked unless allowPrivate=true."},agentId:{type:"string",description:"Agent identifier (optional)."},browserId:{type:"string",description:"Browser instance ID (optional, default 'default')."}},required:["url"]},async execute(e){try{const{agentId:t,browserId:r}=u(e),{session:n,page:s}=m(t,r),o=typeof e.url=="string"?e.url:"";if(!o)return{ok:!1,error:"url is required"};const i=await R(o),a=s.url(),c=Date.now();await s.goto(i,{waitUntil:"domcontentloaded",timeout:M});const l=s.url();try{await n.listenerManager.reattach(s)}catch{}let d="";try{d=await s.title()}catch{}return I(n,{tool:"browser_navigate",args:x({url:i,browserId:r}),beforeUrl:a,afterUrl:l,durationMs:Date.now()-c}),{ok:!0,url:i,finalUrl:l,title:d,browserId:r}}catch(t){return p(t)}}},{kind:"builtin",toolName:"browser_snapshot",exposedName:"browser_snapshot",description:"Capture the active page's accessibility tree as YAML (Playwright ariaSnapshot mode=ai). Interactive elements get [ref=eXX] markers \u2014 pass the eXX string to browser_click / browser_type / browser_select. Re-snapshot after navigation (refs are invalidated on page change). Output truncated to maxChars (default 16000).",inputSchema:{type:"object",properties:{maxChars:{type:"number",description:"Max chars to return (default 16000)."},agentId:{type:"string",description:"Agent identifier (optional)."},browserId:{type:"string",description:"Browser instance ID (optional, default 'default')."}},required:[]},async execute(e){try{const{agentId:t,browserId:r}=u(e),{page:n}=m(t,r),s=typeof e.maxChars=="number"&&e.maxChars>0?Math.floor(e.maxChars):void 0,o=await P(n,{maxChars:s});B(t,r,o);let i="";try{i=await n.title()}catch{}return{ok:!0,snapshot:o,url:n.url(),title:i,browserId:r}}catch(t){return p(t)}}},{kind:"builtin",toolName:"browser_click",exposedName:"browser_click",description:"Click an element identified by ref (from browser_snapshot), text (getByText), role+name (getByRole), or CSS selector. ref is preferred, but text/role are more stable on SPA pages where refs invalidate frequently. If ref fails, the system auto-falls back to the cached role+name from the last snapshot and retries the snapshot once. 10s timeout.",inputSchema:{type:"object",properties:{ref:{type:"string",description:"Element ref from browser_snapshot, e.g. 'e5' (preferred)."},text:{type:"string",description:"Locate element by visible text (getByText). More stable than ref on SPA pages, e.g. 'javaok'."},role:{type:"string",description:"Locate element by ARIA role (e.g. button, link, textbox, listitem). Use with 'name'."},name:{type:"string",description:"Accessible name to combine with 'role' (e.g. role=button + name='Submit')."},selector:{type:"string",description:"CSS selector (subset: tag/.class/#id/[attr]/descendant/>)."},agentId:{type:"string",description:"Agent identifier (optional)."},browserId:{type:"string",description:"Browser instance ID (optional, default 'default')."}},required:[]},async execute(e){try{const{agentId:t,browserId:r}=u(e),{session:n,page:s}=m(t,r),o=typeof e.ref=="string"&&e.ref.trim()?e.ref.trim():void 0,i=typeof e.text=="string"&&e.text.trim()?e.text.trim():void 0,a=typeof e.role=="string"&&e.role.trim()?e.role.trim():void 0,c=typeof e.name=="string"&&e.name?e.name:void 0,l=typeof e.selector=="string"?e.selector:void 0;if(o===void 0&&i===void 0&&a===void 0&&!l)return{ok:!1,error:"Either 'ref', 'text', 'role', or 'selector' is required for browser_click"};const d=o!==void 0?U(t,r,o):void 0,h=await q(s,{ref:o,text:i,role:a,name:c,selector:l,refFallback:d}),w=s.url(),b=Date.now();await h.click({timeout:j});const f=s.url(),g=o!==void 0?`ref=${o}`:i!==void 0?`text=${i}`:a!==void 0?`role=${a}`:`selector=${l}`;return I(n,{tool:"browser_click",args:x({ref:o,text:i,role:a,name:c,selector:l,browserId:r}),beforeUrl:w,afterUrl:f,durationMs:Date.now()-b}),{ok:!0,clicked:g,url:f,browserId:r}}catch(t){return p(t)}}},{kind:"builtin",toolName:"browser_type",exposedName:"browser_type",description:"Type text into an input identified by ref, role+name (getByRole), or CSS selector. Note: 'text' param is the text to type (not for locating); use 'role'+'name' to locate the input. clear=true (default) clears the field first. delay adds per-keystroke delay (ms).",inputSchema:{type:"object",properties:{ref:{type:"string",description:"Element ref from browser_snapshot, e.g. 'e5'."},role:{type:"string",description:"Locate input by ARIA role (e.g. textbox, searchbox). Use with 'name'."},name:{type:"string",description:"Accessible name to combine with 'role'."},selector:{type:"string",description:"CSS selector subset."},text:{type:"string",description:"Text to type (required)."},clear:{type:"boolean",description:"Clear field before typing (default true)."},delay:{type:"number",description:"Per-keystroke delay in ms (default 0)."},agentId:{type:"string",description:"Agent identifier (optional)."},browserId:{type:"string",description:"Browser instance ID (optional, default 'default')."}},required:["text"]},async execute(e){try{const{agentId:t,browserId:r}=u(e),{session:n,page:s}=m(t,r),o=typeof e.ref=="string"&&e.ref.trim()?e.ref.trim():void 0,i=typeof e.role=="string"&&e.role.trim()?e.role.trim():void 0,a=typeof e.name=="string"&&e.name?e.name:void 0,c=typeof e.selector=="string"?e.selector:void 0,l=typeof e.text=="string"?e.text:"";if(!l)return{ok:!1,error:"text is required for browser_type"};if(o===void 0&&i===void 0&&!c)return{ok:!1,error:"Either 'ref', 'role', or 'selector' is required for browser_type"};const d=e.clear!==!1,h=typeof e.delay=="number"&&e.delay>=0?e.delay:0,w=o!==void 0?U(t,r,o):void 0,b=await q(s,{ref:o,role:i,name:a,selector:c,refFallback:w}),f=s.url(),g=Date.now();d&&await b.fill(""),await b.type(l,{delay:h});const S=s.url();return I(n,{tool:"browser_type",args:x({ref:o,role:i,name:a,selector:c,text:l,clear:d,delay:h,browserId:r}),beforeUrl:f,afterUrl:S,durationMs:Date.now()-g}),{ok:!0,typed:l.length,url:S,browserId:r}}catch(t){return p(t)}}},{kind:"builtin",toolName:"browser_select",exposedName:"browser_select",description:"Select an option in a <select> element identified by ref, text (getByText), role+name (getByRole), or CSS selector. value is the option value (string).",inputSchema:{type:"object",properties:{ref:{type:"string",description:"Element ref from browser_snapshot, e.g. 'e5'."},text:{type:"string",description:"Locate element by visible text (getByText)."},role:{type:"string",description:"Locate element by ARIA role (e.g. combobox, listbox). Use with 'name'."},name:{type:"string",description:"Accessible name to combine with 'role'."},selector:{type:"string",description:"CSS selector subset."},value:{type:"string",description:"Option value to select (required)."},agentId:{type:"string",description:"Agent identifier (optional)."},browserId:{type:"string",description:"Browser instance ID (optional, default 'default')."}},required:["value"]},async execute(e){try{const{agentId:t,browserId:r}=u(e),{session:n,page:s}=m(t,r),o=typeof e.ref=="string"&&e.ref.trim()?e.ref.trim():void 0,i=typeof e.text=="string"&&e.text.trim()?e.text.trim():void 0,a=typeof e.role=="string"&&e.role.trim()?e.role.trim():void 0,c=typeof e.name=="string"&&e.name?e.name:void 0,l=typeof e.selector=="string"?e.selector:void 0,d=typeof e.value=="string"?e.value:e.value!==void 0&&e.value!==null?String(e.value):"";if(!d)return{ok:!1,error:"value is required for browser_select"};if(o===void 0&&i===void 0&&a===void 0&&!l)return{ok:!1,error:"Either 'ref', 'text', 'role', or 'selector' is required for browser_select"};const h=o!==void 0?U(t,r,o):void 0,w=await q(s,{ref:o,text:i,role:a,name:c,selector:l,refFallback:h}),b=s.url(),f=Date.now();await w.selectOption(d);const g=s.url();return I(n,{tool:"browser_select",args:x({ref:o,text:i,role:a,name:c,selector:l,value:d,browserId:r}),beforeUrl:b,afterUrl:g,durationMs:Date.now()-f}),{ok:!0,selected:d,url:g,browserId:r}}catch(t){return p(t)}}},{kind:"builtin",toolName:"browser_press_key",exposedName:"browser_press_key",description:"Press a keyboard key on the active page. Examples: 'Enter', 'Escape', 'Tab', 'Control+a', 'ArrowDown'. See Playwright key names for full list.",inputSchema:{type:"object",properties:{key:{type:"string",description:"Key or chord (e.g. 'Enter', 'Control+a')."},agentId:{type:"string",description:"Agent identifier (optional)."},browserId:{type:"string",description:"Browser instance ID (optional, default 'default')."}},required:["key"]},async execute(e){try{const{agentId:t,browserId:r}=u(e),{session:n,page:s}=m(t,r),o=typeof e.key=="string"?e.key:"";if(!o)return{ok:!1,error:"key is required for browser_press_key"};const i=s.url(),a=Date.now();await s.keyboard.press(o);const c=s.url();return I(n,{tool:"browser_press_key",args:x({key:o,browserId:r}),beforeUrl:i,afterUrl:c,durationMs:Date.now()-a}),{ok:!0,key:o,url:c,browserId:r}}catch(t){return p(t)}}},{kind:"builtin",toolName:"browser_scroll",exposedName:"browser_scroll",description:"Scroll the active page. Provide x/y for absolute scroll-to, OR deltaX/deltaY for relative wheel scroll. If both provided, x/y wins.",inputSchema:{type:"object",properties:{x:{type:"number",description:"Absolute scrollX (window.scrollTo)."},y:{type:"number",description:"Absolute scrollY (window.scrollTo)."},deltaX:{type:"number",description:"Relative wheel delta X."},deltaY:{type:"number",description:"Relative wheel delta Y."},agentId:{type:"string",description:"Agent identifier (optional)."},browserId:{type:"string",description:"Browser instance ID (optional, default 'default')."}},required:[]},async execute(e){try{const{agentId:t,browserId:r}=u(e),{session:n,page:s}=m(t,r),o=typeof e.x=="number"&&Number.isFinite(e.x)||typeof e.y=="number"&&Number.isFinite(e.y),i=typeof e.deltaX=="number"&&Number.isFinite(e.deltaX)||typeof e.deltaY=="number"&&Number.isFinite(e.deltaY);if(!o&&!i)return{ok:!1,error:"Provide x/y (absolute) or deltaX/deltaY (relative) for browser_scroll"};const a=s.url(),c=Date.now();let l=0,d=0;if(o){const w=typeof e.x=="number"?e.x:0,b=typeof e.y=="number"?e.y:0;await s.evaluate(([g,S])=>globalThis.scrollTo(g,S),[w,b]);const f=await s.evaluate(()=>globalThis);l=f.scrollX,d=f.scrollY}else{const w=typeof e.deltaX=="number"?e.deltaX:0,b=typeof e.deltaY=="number"?e.deltaY:0;await s.mouse.wheel(w,b);const f=await s.evaluate(()=>globalThis);l=f.scrollX,d=f.scrollY}const h=s.url();return I(n,{tool:"browser_scroll",args:x({x:e.x,y:e.y,deltaX:e.deltaX,deltaY:e.deltaY,browserId:r}),beforeUrl:a,afterUrl:h,durationMs:Date.now()-c}),{ok:!0,scrollX:l,scrollY:d,browserId:r}}catch(t){return p(t)}}},{kind:"builtin",toolName:"browser_screenshot",exposedName:"browser_screenshot",description:"Capture a single screenshot of the active page. Returns dataUrl (base64-encoded). fullPage=true captures the entire scrollable page (may be large).",inputSchema:{type:"object",properties:{fullPage:{type:"boolean",description:"Capture entire scrollable page (default false)."},format:{type:"string",enum:["jpeg","png"],description:"Image format (default 'jpeg')."},quality:{type:"number",description:"JPEG quality 0-100 (default 80, ignored for png)."},agentId:{type:"string",description:"Agent identifier (optional)."},browserId:{type:"string",description:"Browser instance ID (optional, default 'default')."}},required:[]},async execute(e){try{const{agentId:t,browserId:r}=u(e),{page:n}=m(t,r),s=e.fullPage===!0,o=e.format==="png"||e.format==="jpeg"?e.format:"jpeg",i=typeof e.quality=="number"&&e.quality>=0&&e.quality<=100?e.quality:80,c=(await n.screenshot({fullPage:s,type:o,quality:o==="jpeg"?i:void 0})).toString("base64"),l=`data:image/${o};base64,${c}`,d=n.viewportSize();return{ok:!0,dataUrl:l,width:d?.width??0,height:d?.height??0,url:n.url(),browserId:r}}catch(t){return p(t)}}},{kind:"builtin",toolName:"browser_close",exposedName:"browser_close",description:"Close the default browser session (browserId='default') for the current agent. Returns the step archive (recent operations). To close a non-default browser session, use browser_session_close({browserId}).",inputSchema:{type:"object",properties:{agentId:{type:"string",description:"Agent identifier (optional)."}},required:[]},async execute(e){try{const{agentId:t}=u(e),r=y.get(t,v);if(!r)return{ok:!1,error:`Browser session not launched for ${t}/${v}. Call browser_launch first.`};const n=r.archive.toJSON();return await y.close(t,v),{ok:!0,browserId:v,archive:n}}catch(t){return p(t)}}},{kind:"builtin",toolName:"browser_list_sessions",exposedName:"browser_list_sessions",description:"List all browser sessions for the current agent. Returns an array of {browserId, pageCount, currentUrl, currentTitle, launchedAt, lastActivityAt}. Use this to discover which browserIds are currently active before calling browser_session_close.",inputSchema:{type:"object",properties:{agentId:{type:"string",description:"Agent identifier (optional)."}},required:[]},async execute(e){try{const{agentId:t}=u(e),r=y.listSessions(t);return{ok:!0,agentId:t,sessions:r.map(n=>({browserId:n.browserId,pageCount:n.pageCount,activePageIndex:n.activePageIndex,currentUrl:n.currentUrl,currentTitle:n.currentTitle,launchedAt:n.launchedAt,lastActivityAt:n.lastActivityAt,wsUrl:_?_.getUrl(t,n.browserId):void 0}))}}catch(t){return p(t)}}},{kind:"builtin",toolName:"browser_session_close",exposedName:"browser_session_close",description:"Close a specific browser session by browserId for the current agent. Returns the step archive of that session. Use browser_list_sessions to enumerate active browserIds. For the default session, you can also call browser_close (alias for browser_session_close with browserId='default').",inputSchema:{type:"object",properties:{browserId:{type:"string",description:"Browser instance ID to close (default 'default'). Required for non-default sessions."},agentId:{type:"string",description:"Agent identifier (optional)."}},required:[]},async execute(e){try{const{agentId:t,browserId:r}=u(e),n=y.get(t,r);if(!n)return{ok:!1,error:`Browser session not launched for ${t}/${r}. Call browser_launch first.`};const s=n.archive.toJSON();return await y.close(t,r),{ok:!0,browserId:r,archive:s}}catch(t){return p(t)}}}]}export{te as createBuiltInBrowserTools};
2
+
@@ -0,0 +1,24 @@
1
+
2
+ import type { Locator, Page } from "playwright";
3
+
4
+ export interface LocateOptions {
5
+
6
+ ref?: string;
7
+
8
+ selector?: string;
9
+
10
+ text?: string;
11
+
12
+ role?: string;
13
+
14
+ name?: string;
15
+
16
+ refFallback?: {
17
+ role: string;
18
+ name: string;
19
+ };
20
+ }
21
+
22
+ export declare function validateSelector(selector: string): void;
23
+
24
+ export declare function locateElement(page: Page, options: LocateOptions): Promise<Locator>;
@@ -0,0 +1,2 @@
1
+ import{findElementByRef as h,locateByRoleName as u}from"./snapshot-serializer.js";function E(t){if(!t||typeof t!="string"||!t.trim())throw new Error("selector must be a non-empty string");if(/[,~+]/.test(t))throw new Error(`selector '${t}' contains disallowed combinator (,/~/+); only descendant (space) and child (>) are allowed`);if(/::?[\w-]+/.test(t))throw new Error(`selector '${t}' contains pseudo-class or pseudo-element; not allowed`);if(/:\/\//.test(t))throw new Error(`selector '${t}' contains a protocol-like token; not allowed`)}async function x(t,p){const{ref:i,selector:a,text:s,role:l,name:f,refFallback:o}=p,c=typeof i=="string"&&i.trim(),y=typeof a=="string"&&a.trim(),m=typeof s=="string"&&s.trim(),w=typeof l=="string"&&l.trim();if(!c&&!y&&!m&&!w)throw new Error("Either 'ref', 'selector', 'text', or 'role' must be provided to locate an element");if(c){const r=i.trim();let e=await h(t,r);if(e||o&&(e=u(t,o.role,o.name),e)||(e=await h(t,r),e))return e;const n=o?` (fallback role=${o.role} name="${o.name}" also not found)`:"";throw new Error(`Element with ref=${r} not found in accessibility tree${n}. Re-snapshot the page and use a fresh ref, or locate via 'text'/'role' which are more stable on SPA pages.`)}if(m){const r=s.trim();return t.getByText(r).first()}if(w){const r=l.trim(),n=u(t,r,typeof f=="string"&&f?f:void 0);if(!n)throw new Error(`Invalid role '${r}'`);return n}const d=a;return E(d),t.locator(d).first()}export{x as locateElement,E as validateSelector};
2
+
@@ -0,0 +1,17 @@
1
+
2
+ import type { Page } from "playwright";
3
+
4
+ export interface PickerResult {
5
+ selector: string;
6
+ found: boolean;
7
+ tag: string;
8
+ text: string;
9
+ }
10
+
11
+ export declare function installPickerOverlay(page: Page): Promise<void>;
12
+
13
+ export declare function removePickerOverlay(page: Page): Promise<void>;
14
+
15
+ export declare function pickElementAt(page: Page, x: number, y: number): Promise<PickerResult>;
16
+
17
+ export declare function pickElementParent(page: Page): Promise<PickerResult>;
@@ -0,0 +1,2 @@
1
+ const u="__aws_picker_box",E="__aws_picker_last_el";async function v(s){await s.evaluate(o=>{const i=globalThis.document;if(!i||i.getElementById(o))return;const n=i.createElement("div");n.id=o,n.style.cssText=["position:fixed","top:0","left:0","width:0","height:0","border:2px solid #3b82f6","background:rgba(59,130,246,0.12)","border-radius:2px","box-sizing:border-box","pointer-events:none","z-index:2147483647","transition:all 0.06s linear","display:none"].join(";"),(i.body??i.documentElement).appendChild(n)},u)}async function w(s){await s.evaluate(o=>{const n=globalThis.document?.getElementById(o.boxId);n&&n.remove(),delete globalThis[o.lastElProp]},{boxId:u,lastElProp:E})}async function C(s,o,i){return _(s,{mode:"point",x:o,y:i})}async function T(s){return _(s,{mode:"parent"})}async function _(s,o){const i=o.mode==="point"?{mode:o.mode,x:o.x,y:o.y,boxId:u,lastElProp:E}:{mode:o.mode,x:0,y:0,boxId:u,lastElProp:E};return await s.evaluate(n=>{const d=globalThis.document,x={selector:"",found:!1,tag:"",text:""};if(!d)return x;const r=d.getElementById(n.boxId),g=globalThis,b=e=>{const t=globalThis.CSS;return t&&t.escape?t.escape(e):e.replace(/([^\w-])/g,"\\$1")},k=e=>!(!e||e.length>24||/[0-9]{6,}/.test(e)||/^(css-|sc-|ember-|jsx-|svelte-)/.test(e)||/^[0-9]/.test(e)),I=e=>{if(e.id)return"#"+b(e.id);const t=[];let l=e;for(let P=0;P<4&&l&&l.nodeType===1;P++){if(l.id){t.unshift("#"+b(l.id));break}let y=l.tagName.toLowerCase();const p=[];for(const c of l.classList)if(k(c)&&p.push(c),p.length>=2)break;p.length>0&&(y+="."+p.map(b).join("."));const a=l.parentElement;if(a){const c=[];for(let f=0;f<a.children.length;f++)a.children[f].tagName===l.tagName&&c.push(a.children[f]);c.length>1&&(y+=":nth-of-type("+(c.indexOf(l)+1)+")")}t.unshift(y);try{if(d.querySelector(t.join(" > "))===e)break}catch{}l=a}return t.join(" > ")},h=e=>{if(r){const t=e.getBoundingClientRect();r.style.left=t.left+"px",r.style.top=t.top+"px",r.style.width=t.width+"px",r.style.height=t.height+"px",r.style.display="block"}return g[n.lastElProp]=e,{selector:I(e),found:!0,tag:e.tagName.toLowerCase(),text:(e.textContent??"").replace(/\s+/g," ").trim().slice(0,80)}};if(n.mode==="parent"){const e=g[n.lastElProp];if(!e)return x;const t=e.parentElement;return h(t||e)}const m=d.elementFromPoint(n.x,n.y);return!m||m.id===n.boxId?(r&&(r.style.display="none"),x):h(m)},i)}export{v as installPickerOverlay,C as pickElementAt,T as pickElementParent,w as removePickerOverlay};
2
+
@@ -0,0 +1,34 @@
1
+
2
+ import type { BrowserPool } from "./browser-pool.js";
3
+ import type { JsonObject } from "./types.js";
4
+
5
+ export interface DispatchResult {
6
+ ok: boolean;
7
+ error?: string;
8
+ }
9
+ export declare class InputDispatcher {
10
+ private readonly pool;
11
+ constructor(pool: BrowserPool);
12
+
13
+ private mouseMoveChain;
14
+
15
+ private pendingMouseMoveCoord;
16
+
17
+ private readonly buttonsMaskBySession;
18
+
19
+ private maskKey;
20
+ private getButtonsMask;
21
+ private setButtonsMask;
22
+
23
+ clearSession(agentId: string, browserId: string): void;
24
+
25
+ private flushMouseMoves;
26
+
27
+ acquireTakeover(agentId: string, browserId: string, clientId: string): boolean;
28
+
29
+ releaseTakeover(agentId: string, browserId: string, clientId: string): void;
30
+
31
+ dispatch(agentId: string, browserId: string, action: string, payload: JsonObject): Promise<DispatchResult>;
32
+
33
+ private executeAction;
34
+ }
@@ -0,0 +1,2 @@
1
+ const l=1e4;function M(h){const e=typeof h=="string"?h.toLowerCase():"left";return e==="right"||e==="middle"?e:"left"}function m(h){switch(h){case"right":return 2;case"middle":return 4;default:return 1}}function y(h){return h&1?"left":h&2?"right":h&4?"middle":"none"}function k(h){const e=typeof h.x=="number"?h.x:void 0,a=typeof h.y=="number"?h.y:void 0;if(!(e===void 0||a===void 0))return{x:e,y:a}}function w(h,e){return new Promise((a,n)=>{const o=setTimeout(()=>n(new Error("input timeout")),e);h.then(c=>{clearTimeout(o),a(c)},c=>{clearTimeout(o),n(c)})})}class p{constructor(e){this.pool=e,this.mouseMoveChain=Promise.resolve(),this.pendingMouseMoveCoord=null,this.buttonsMaskBySession=new Map}maskKey(e,a){return`${e}::${a}`}getButtonsMask(e){return this.buttonsMaskBySession.get(this.maskKey(e.agentId,e.browserId))??0}setButtonsMask(e,a){this.buttonsMaskBySession.set(this.maskKey(e.agentId,e.browserId),a)}clearSession(e,a){this.buttonsMaskBySession.delete(this.maskKey(e,a))}async flushMouseMoves(){try{await this.mouseMoveChain}catch{}}acquireTakeover(e,a,n){const o=this.pool.get(e,a);return!o||o.humanTakeover&&o.takeoverClientId!==n?!1:(o.humanTakeover=!0,o.takeoverClientId=n,this.buttonsMaskBySession.set(this.maskKey(e,o.browserId),0),!0)}releaseTakeover(e,a,n){const o=this.pool.get(e,a);o&&o.takeoverClientId===n&&(o.humanTakeover=!1,o.takeoverClientId=void 0,this.buttonsMaskBySession.set(this.maskKey(e,o.browserId),0))}async dispatch(e,a,n,o){const c=this.pool.get(e,a);if(!c)return{ok:!1,error:`Browser session not found: ${e}/${a}`};if(!c.humanTakeover)return{ok:!1,error:"not in takeover mode"};const d=c.pages[c.activePageIndex];if(!d||d.isClosed())return{ok:!1,error:"active page is closed"};try{if(await w(this.executeAction(d,n,o,c),1e4),c.lastActivityAt=Date.now(),n!=="mousemove"){const r=c.screencast.forceCaptureAndPush?.(d);r&&r.catch(()=>{})}return{ok:!0}}catch(r){return{ok:!1,error:r instanceof Error?r.message:String(r)}}}async executeAction(e,a,n,o){const c=e.mouse,d=e.keyboard,r=o.screencast.getCurrentCdpSession?.();switch(a){case"click":{const t=k(n);if(!t)throw new Error("click requires x,y");const u=M(n.button);if(r){const i=m(u);let s=this.getButtonsMask(o);await r.send("Input.dispatchMouseEvent",{type:"mouseMoved",x:t.x,y:t.y,button:y(s),buttons:s}),s|=i,await r.send("Input.dispatchMouseEvent",{type:"mousePressed",x:t.x,y:t.y,button:u,buttons:s,clickCount:1}),s&=~i,await r.send("Input.dispatchMouseEvent",{type:"mouseReleased",x:t.x,y:t.y,button:u,buttons:s,clickCount:1}),this.setButtonsMask(o,s)}else await c.click(t.x,t.y,{button:u});return}case"dblclick":{const t=k(n);if(!t)throw new Error("dblclick requires x,y");if(r){const u=M(n.button),i=m(u);let s=this.getButtonsMask(o);for(let f=0;f<2;f++)s|=i,await r.send("Input.dispatchMouseEvent",{type:"mousePressed",x:t.x,y:t.y,button:u,buttons:s,clickCount:f+1}),s&=~i,await r.send("Input.dispatchMouseEvent",{type:"mouseReleased",x:t.x,y:t.y,button:u,buttons:s,clickCount:f+1});this.setButtonsMask(o,s)}else await c.dblclick(t.x,t.y);return}case"mousedown":{const t=k(n);if(!t)throw new Error("mousedown requires x,y");const u=M(n.button);if(await this.flushMouseMoves(),r){const i=m(u);let s=this.getButtonsMask(o);await r.send("Input.dispatchMouseEvent",{type:"mouseMoved",x:t.x,y:t.y,button:y(s),buttons:s}),s|=i,this.setButtonsMask(o,s),await r.send("Input.dispatchMouseEvent",{type:"mousePressed",x:t.x,y:t.y,button:u,buttons:s,clickCount:1})}else await c.move(t.x,t.y),await c.down({button:u});return}case"mousemove":{const t=k(n);if(!t)throw new Error("mousemove requires x,y");this.pendingMouseMoveCoord={page:e,x:t.x,y:t.y},this.mouseMoveChain=this.mouseMoveChain.catch(()=>{}).then(async()=>{const u=this.pendingMouseMoveCoord;if(!u)return;this.pendingMouseMoveCoord=null;const i=this.getButtonsMask(o);if(r)try{await r.send("Input.dispatchMouseEvent",{type:"mouseMoved",x:u.x,y:u.y,button:y(i),buttons:i});return}catch{}await u.page.mouse.move(u.x,u.y)});return}case"mouseup":{const t=k(n);if(!t)throw new Error("mouseup requires x,y");const u=M(n.button);if(await this.flushMouseMoves(),r){const i=m(u);let s=this.getButtonsMask(o);await r.send("Input.dispatchMouseEvent",{type:"mouseMoved",x:t.x,y:t.y,button:y(s),buttons:s}),s&=~i,this.setButtonsMask(o,s),await r.send("Input.dispatchMouseEvent",{type:"mouseReleased",x:t.x,y:t.y,button:u,buttons:s,clickCount:1})}else await c.move(t.x,t.y),await c.up({button:u});return}case"scroll":{const t=typeof n.deltaX=="number"?n.deltaX:0,u=typeof n.deltaY=="number"?n.deltaY:0,i=k(n),s=this.getButtonsMask(o);if(i&&r)try{await r.send("Input.dispatchMouseEvent",{type:"mouseMoved",x:i.x,y:i.y,button:y(s),buttons:s}),await r.send("Input.dispatchMouseEvent",{type:"mouseWheel",x:i.x,y:i.y,deltaX:t,deltaY:u,button:y(s),buttons:s});return}catch{}i&&await c.move(i.x,i.y),await c.wheel(t,u);return}case"type":{const t=typeof n.text=="string"?n.text:"";if(!t)throw new Error("type requires non-empty text");if(r)try{await r.send("Input.insertText",{text:t});return}catch{}await d.type(t);return}case"press_key":{const t=typeof n.key=="string"?n.key:"";if(!t)throw new Error("press_key requires key");await d.press(t);return}default:throw new Error(`Unknown input action: ${a}`)}}}export{p as InputDispatcher};
2
+
@@ -0,0 +1,32 @@
1
+
2
+ import type { PageListener } from "./page-listener-types.js";
3
+
4
+ export interface ListenerStoreConfig {
5
+
6
+ listenerDir: string;
7
+ }
8
+
9
+ export declare class ListenerStore {
10
+ private readonly config;
11
+
12
+ private readonly cache;
13
+
14
+ private readonly loaded;
15
+ constructor(config: ListenerStoreConfig);
16
+
17
+ private filePath;
18
+
19
+ load(scopeKey: string): Promise<PageListener[]>;
20
+
21
+ save(scopeKey: string, listeners: PageListener[]): Promise<void>;
22
+
23
+ add(scopeKey: string, listener: PageListener): Promise<void>;
24
+
25
+ update(scopeKey: string, listener: PageListener): Promise<void>;
26
+
27
+ remove(scopeKey: string, listenerId: string): Promise<void>;
28
+
29
+ clear(scopeKey: string): Promise<void>;
30
+
31
+ invalidate(scopeKey: string): void;
32
+ }
@@ -0,0 +1,2 @@
1
+ import{mkdir as d,readFile as h,writeFile as o,rename as l,unlink as c}from"node:fs/promises";import{dirname as f,join as u}from"node:path";function w(n){const t=n.replace(/[^a-zA-Z0-9._-]/g,"_");return t===""||/^\.+$/.test(t)?"_":t}class p{constructor(t){this.config=t,this.cache=new Map,this.loaded=new Set}filePath(t){return u(this.config.listenerDir,w(t),"listeners.json")}async load(t){if(this.loaded.has(t))return this.cache.get(t)??[];let e=[];try{const s=await h(this.filePath(t),"utf-8"),i=JSON.parse(s);i&&Array.isArray(i.listeners)&&(e=i.listeners.filter(a=>!!a&&typeof a.id=="string"&&typeof a.selector=="string"&&typeof a.pathPattern=="string"&&typeof a.prompt=="string"&&typeof a.enabled=="boolean"&&typeof a.createdAt=="number"))}catch{}return this.cache.set(t,e),this.loaded.add(t),e}async save(t,e){this.cache.set(t,e.map(r=>({...r}))),this.loaded.add(t);const s={version:1,listeners:e},i=this.filePath(t);await d(f(i),{recursive:!0});const a=`${i}.tmp`;await o(a,JSON.stringify(s,null,2),"utf-8"),await l(a,i)}async add(t,e){const s=await this.load(t),i=s.findIndex(a=>a.id===e.id);i>=0?s[i]={...e}:s.push({...e}),await this.save(t,s)}async update(t,e){const s=await this.load(t),i=s.findIndex(a=>a.id===e.id);i<0||(s[i]={...e},await this.save(t,s))}async remove(t,e){const s=await this.load(t),i=s.filter(a=>a.id!==e);i.length!==s.length&&await this.save(t,i)}async clear(t){this.cache.delete(t),this.loaded.delete(t);try{await c(this.filePath(t))}catch{}}invalidate(t){this.cache.delete(t),this.loaded.delete(t)}}export{p as ListenerStore};
2
+
@@ -0,0 +1,106 @@
1
+
2
+ import type { Page } from "playwright";
3
+ import type { ListenerChangeType, ListenerId, ListenerTriggerEvent, PageListener, PageListenerInfo, PageListenerManagerConfig } from "./page-listener-types.js";
4
+ export declare function matchPath(target: string, pattern: string): boolean;
5
+
6
+ export declare function extractPathname(url: string): string;
7
+
8
+ export declare function derivePathPatternFromUrl(page: Page | null): string;
9
+
10
+ interface DetectNodeLike {
11
+ nodeType: number;
12
+ }
13
+ interface DetectMutationLike {
14
+ type: string;
15
+ addedNodes?: ArrayLike<DetectNodeLike> | null;
16
+ removedNodes?: ArrayLike<DetectNodeLike> | null;
17
+ }
18
+
19
+ export declare function nodesContainElement(nodes: ArrayLike<DetectNodeLike> | null | undefined): boolean;
20
+
21
+ export declare function detectChangeTypes(mutations: ArrayLike<DetectMutationLike> | null | undefined): ListenerChangeType[];
22
+
23
+ export declare function detectChangeType(mutations: ArrayLike<DetectMutationLike> | null | undefined): ListenerChangeType;
24
+
25
+ export interface AddListenerParams {
26
+ selector: string;
27
+ pathPattern: string;
28
+ prompt: string;
29
+ enabled?: boolean;
30
+
31
+ changeTypes?: ListenerChangeType[];
32
+ }
33
+
34
+ export interface UpdateListenerParams {
35
+ listenerId: ListenerId;
36
+ selector?: string;
37
+ pathPattern?: string;
38
+ prompt?: string;
39
+ enabled?: boolean;
40
+
41
+ changeTypes?: ListenerChangeType[];
42
+ }
43
+
44
+ export declare class PageListenerManager {
45
+ private readonly agentId;
46
+ private readonly browserId;
47
+ private readonly config;
48
+ private readonly listeners;
49
+ private readonly eventQueue;
50
+ private readonly triggerMeta;
51
+
52
+ private readonly pendingChangeTypes;
53
+
54
+ private readonly existsLatched;
55
+
56
+ private readonly attachedPages;
57
+
58
+ private readonly navHandledPages;
59
+ private currentPage;
60
+ constructor(agentId: string, browserId: string, config?: PageListenerManagerConfig);
61
+
62
+ getCurrentPage(): Page | null;
63
+
64
+ loadFromStore(): Promise<number>;
65
+
66
+ attach(page: Page): Promise<void>;
67
+
68
+ reattach(page: Page): Promise<void>;
69
+
70
+ addListener(params: AddListenerParams): Promise<PageListener>;
71
+
72
+ updateListener(params: UpdateListenerParams): Promise<PageListener | undefined>;
73
+
74
+ removeListener(id: ListenerId): Promise<boolean>;
75
+
76
+ toggleListener(id: ListenerId, enabled?: boolean): Promise<PageListener | undefined>;
77
+
78
+ listListeners(): Promise<PageListenerInfo[]>;
79
+
80
+ hasPendingEvents(): boolean;
81
+
82
+ enqueueEvent(event: ListenerTriggerEvent): void;
83
+
84
+ dequeueEvent(): ListenerTriggerEvent | undefined;
85
+
86
+ collectEvents(): ListenerTriggerEvent[];
87
+
88
+ scanExistsAtPoll(): Promise<ListenerTriggerEvent[]>;
89
+
90
+ dispose(): Promise<void>;
91
+
92
+ setHighlightEnabled(enabled: boolean): Promise<void>;
93
+
94
+ private ensureExposed;
95
+
96
+ private attachNavigationHandler;
97
+
98
+ private handleDomChange;
99
+
100
+ private installOnPage;
101
+
102
+ private uninstallOnPage;
103
+
104
+ private elementExists;
105
+ }
106
+ export {};