import type { ConfigStore } from '../../../config/store.ts' import { defineTool, type RegisteredTool } from '../../server.ts' import { type AttachState, disposeAttached } from './attach.ts' import { type CaptureStore, resetCaptures } from './capture.ts' import { type LiveShareRef, stopLiveShare } from './live-view.ts' export function sessionTools( attachRef: { current?: AttachState, config: ConfigStore }, captures: CaptureStore, liveShare: LiveShareRef, ): RegisteredTool[] { return [ defineTool>( { name: 'get_session', description: 'Report the current attach state, the live captureIds, and the ' + 'persisted browser config. Call it to check whether a browser is ' + 'still attached before reattaching, or to recover a captureId.', inputSchema: { type: 'object', properties: {} }, }, async() => ({ config: attachRef.config.read()?.browser_agent, attached: Boolean(attachRef.current), mode: attachRef.current?.mode, port: attachRef.current?.port, liveViewUrl: attachRef.current?.liveViewUrl, browserSessionId: attachRef.current?.browserSessionId, orgId: attachRef.current?.orgId, captures: [...captures.sessions.keys()], activeTab: captures.activeTab, // The public share, when one is open — so a caller that lost the // URL can recover it instead of opening a second tunnel. ...(liveShare.current ? { sharedView: { url: liveShare.current.url }, } : {}), }), ), defineTool>( { name: 'dispose_browser', description: 'Stop captures, disconnect the current browser, and immediately ' + 'delete a Builder-hosted Popcorn session. Custom CDP sessions ' + 'are disconnected but never deleted by Reclaim. Also closes a ' + 'public live view opened by share_browser_view.', inputSchema: { type: 'object', properties: {} }, }, async() => { await resetAll(attachRef, captures, liveShare) return { disposed: true } }, ), defineTool>( { name: 'reset_session', description: 'Stop all captures and clear capture state — drops every ' + 'session\'s drafts and their in-memory secrets, and closes a ' + 'public live view opened by share_browser_view.', inputSchema: { type: 'object', properties: {} }, }, async() => { await resetAll(attachRef, captures, liveShare) return { reset: true } }, ), ] } /** * Release everything a session holds: the public tunnel, the captures, and the * browser itself. * * Exported because a session can end WITHOUT anyone calling `dispose_browser` — * the developer closes their agent, the client kills the server, the transport * drops. Nothing runs on its own then, so `start.ts` runs this. Without it a * container keeps running and, worse, a public URL to a live browser outlives * the session that created it. */ export async function resetAll( attachRef: { current?: AttachState, config: ConfigStore }, captures: CaptureStore, liveShare: LiveShareRef, ) { // Before the captures: a public URL must never outlive the browser it points // at. await stopLiveShare(liveShare) await resetCaptures(captures) await disposeAttached(attachRef) }