Files
BrowserOS/apps/server/src/tools/framework.ts
Nikhil 8a38e90e24 fix: move session dirs to ~/.browseros/sessions and update skill paths (#494)
* chore: bump server version

* fix: move session dirs to ~/.browseros/sessions and update skill paths

Session directories now live under ~/.browseros/sessions/{conversationId}/
instead of executionDir/sessions/. Adds 30-day cleanup for stale sessions
at server startup. Updates 6 default skills to reference the working
directory instead of hardcoding ~/Downloads/.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor: rename sessionExecutionDir to workingDir across server

Consistent naming for the per-conversation working directory:
- ResolvedAgentConfig.sessionExecutionDir → workingDir
- ToolDirectories.executionDir → workingDir
- resolveExecutionPath() → resolveWorkingPath()
- buildBrowserToolSet param: executionDir → workingDir

Server-level executionDir (DB, logs) unchanged.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: address PR review — restore emoji folder name, refresh session mtime

- Revert "Read Later" back to "📚 Read Later" to avoid creating
  duplicate bookmark folders for existing users
- Touch session dir mtime on each message via utimes() so cleanup
  correctly reflects last activity, not just directory creation time

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: address PR review round 2 — remove dead executionDir, fix emoji

- Remove executionDir from ChatServiceDeps and ChatRouteDeps since
  resolveSessionDir now uses getSessionsDir() directly
- Fix missed emoji in notification format template

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 16:41:47 -07:00

88 lines
2.0 KiB
TypeScript

import { resolve } from 'node:path'
import type { z } from 'zod'
import type { Browser } from '../browser/browser'
import { ToolResponse, type ToolResult } from './response'
export interface ToolDefinition {
name: string
description: string
input: z.ZodType
output?: z.ZodType
handler: ToolHandler
}
export type ToolHandler = (
args: unknown,
ctx: ToolContext,
response: ToolResponse,
) => Promise<void>
export interface ToolDirectories {
workingDir: string
resourcesDir?: string
}
export type ToolContext = {
browser: Browser
directories: ToolDirectories
}
export function resolveWorkingPath(
ctx: ToolContext,
targetPath: string,
cwd?: string,
): string {
return resolve(cwd ?? ctx.directories.workingDir, targetPath)
}
export function defineTool<
TInput extends z.ZodType,
TOutput extends z.ZodType | undefined = undefined,
>(config: {
name: string
description: string
input: TInput
output?: TOutput
handler: (
args: z.infer<TInput>,
ctx: ToolContext,
response: ToolResponse,
) => Promise<void>
}): ToolDefinition {
return config as ToolDefinition
}
export async function executeTool(
tool: ToolDefinition,
args: unknown,
ctx: ToolContext,
signal: AbortSignal,
): Promise<ToolResult> {
const response = new ToolResponse()
if (signal.aborted) {
response.error('Request was aborted')
return response.toResult()
}
try {
await tool.handler(args, ctx, response)
} catch (err) {
const message = err instanceof Error ? err.message : String(err)
response.error(`Internal error in ${tool.name}: ${message}`)
}
const result = await response.build(ctx.browser)
// TODO: nikhil -- maybe add to tool context instead of ugly args casting
const pageId = (args as Record<string, unknown>).page
if (typeof pageId === 'number') {
const tabId = ctx.browser.getTabIdForPage(pageId)
if (tabId !== undefined) {
result.metadata = { ...result.metadata, tabId }
}
}
return result
}