diff --git a/.gitignore b/.gitignore index 9792407a..0ffad5c5 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,5 @@ /CLAUDE.md .takumi/todos /tmp +/plugins/*/node_modules +/plugins/*/dist diff --git a/package.json b/package.json index 15caecfc..8afee543 100644 --- a/package.json +++ b/package.json @@ -71,7 +71,6 @@ "@openai/agents-extensions": "^0.0.16", "@openrouter/ai-sdk-provider": "^0.7.5", "@sinclair/typebox": "^0.34.38", - "@stagewise/agent-interface": "^0.2.3", "@trivago/prettier-plugin-sort-imports": "^5.2.2", "@types/bun": "^1.2.21", "@types/debug": "^4.1.12", diff --git a/plugins/plugin-stagewise/package.json b/plugins/plugin-stagewise/package.json new file mode 100644 index 00000000..4741ec74 --- /dev/null +++ b/plugins/plugin-stagewise/package.json @@ -0,0 +1,21 @@ +{ + "name": "@neovate/plugin-stagewise", + "version": "0.0.1", + "description": "Stagewise plugin for neovate", + "type": "module", + "main": "dist/index.mjs", + "types": "dist/index.d.ts", + "source": "src/index.ts", + "scripts": { + "build": "bun build src/index.ts --minify --external @neovate/code --outfile dist/index.mjs --target=node", + "dev": "bun build src/index.ts --watch --external @neovate/code --outfile dist/index.mjs --target=node" + }, + "devDependencies": { + "@stagewise/agent-interface": "^0.2.3", + "@neovate/code": "workspace:*" + }, + "keywords": [], + "author": "", + "license": "ISC", + "packageManager": "pnpm@10.13.1" +} diff --git a/src/plugins/stagewise.ts b/plugins/plugin-stagewise/src/index.ts similarity index 78% rename from src/plugins/stagewise.ts rename to plugins/plugin-stagewise/src/index.ts index 9d23a457..b865e84a 100644 --- a/src/plugins/stagewise.ts +++ b/plugins/plugin-stagewise/src/index.ts @@ -1,24 +1,21 @@ -// @ts-nocheck +import { type Context, type Plugin, _Project as Project } from '@neovate/code'; import { type AgentServer, AgentStateType, createAgentServer, } from '@stagewise/agent-interface/agent'; import createDebug from 'debug'; -import { Context } from '../context'; -import { type Plugin } from '../plugin'; -import { Service } from '../service'; -import { relativeToHome } from '../utils/path'; +import os from 'os'; const debug = createDebug('neovate:plugins:stagewise'); -type CreateStagewisePluginOpts = {}; - -export const createStagewisePlugin = (opts: CreateStagewisePluginOpts) => { +export const createStagewisePlugin = () => { let sw: StagewiseAgent | null = null; + return { name: 'stagewise', - async cliStart() { + + async initialized(this: Context) { try { sw = new StagewiseAgent({ context: this, @@ -29,10 +26,12 @@ export const createStagewisePlugin = (opts: CreateStagewisePluginOpts) => { debug('Failed to start Stagewise agent:', error); } }, + async destroy() { await sw?.stop(); }, - async status() { + + async status(this: Context) { const port = sw?.port; const status = port ? `Connected, port: ${port}` : 'Disconnected'; return { @@ -50,8 +49,8 @@ export interface StagewiseAgentOpts { export class StagewiseAgent { private context: Context; - private service?: Service; private server: AgentServer | null = null; + private activeProjects: Map = new Map(); public port: number = 0; constructor(opts: StagewiseAgentOpts) { @@ -59,12 +58,6 @@ export class StagewiseAgent { } async start() { - // Create a separate service for Stagewise with independent chat history - this.service = await Service.create({ - agentType: 'code', - context: this.context, - }); - this.server = await createAgentServer(); this.server.setAgentName(`${this.context.productName} AI Agent`); @@ -88,6 +81,17 @@ export class StagewiseAgent { await this.server.wss.close(); await this.server.server.close(); } + this.activeProjects.clear(); + } + + private getOrCreateProject(connectionId: string): Project { + if (!this.activeProjects.has(connectionId)) { + const project = new Project({ + context: this.context, + }); + this.activeProjects.set(connectionId, project); + } + return this.activeProjects.get(connectionId)!; } private async processUserMessage(message: any) { @@ -116,13 +120,12 @@ export class StagewiseAgent { debug('Processing user message:', userText); const { metadata } = message; + const connectionId = message.connectionId || 'default'; - // Build enhanced content with selected elements context let enhancedContent = userText; enhancedContent += `\n\nIMPORTANT: don't need to run test or build to check if the code is working, speed is more important.`; - // Add current page context if (metadata.currentUrl) { enhancedContent += `\n\nCurrent page context:`; enhancedContent += `\n- URL: ${metadata.currentUrl}`; @@ -131,7 +134,6 @@ export class StagewiseAgent { } } - // Add selected elements context if (metadata.selectedElements && metadata.selectedElements.length > 0) { enhancedContent += `\n\nSelected elements context (${metadata.selectedElements.length} element(s)):`; @@ -154,7 +156,6 @@ export class StagewiseAgent { if (val && val.value && val.value._debugSource) { enhancedContent += `\n- ${property} value debug source: ${val.value._debugSource}`; } - // Add other useful properties if (val && typeof val === 'string' && val.length < 100) { enhancedContent += `\n- ${property}: ${val}`; } @@ -168,18 +169,26 @@ export class StagewiseAgent { 'Generating response...', ); - const { query } = await import('../query'); - const { isReasoningModel } = await import('../provider'); - - const result = await query({ - input: [{ role: 'user', content: enhancedContent }], - service: this.service!, - thinking: isReasoningModel(this.service!.context.config.model), + const project = this.getOrCreateProject(connectionId); + + const result = await project.send(enhancedContent, { + onToolApprove: () => Promise.resolve(true), + onTextDelta: async (text: string) => { + this.server!.interface.messaging.set([ + { + type: 'text', + text, + }, + ]); + }, }); - let response = - result.finalText || - "I processed your request but didn't generate a text response."; + let response: string; + if (result.success) { + response = result.data.text || 'I processed your request successfully.'; + } else { + response = `I encountered an error: ${result.error.message}`; + } this.server!.interface.messaging.set([ { @@ -210,3 +219,7 @@ export class StagewiseAgent { } } } + +export function relativeToHome(p: string) { + return p.replace(os.homedir(), '~'); +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0b79fe96..ac91da0e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -67,9 +67,6 @@ importers: '@sinclair/typebox': specifier: ^0.34.38 version: 0.34.38 - '@stagewise/agent-interface': - specifier: ^0.2.3 - version: 0.2.3 '@trivago/prettier-plugin-sort-imports': specifier: ^5.2.2 version: 5.2.2(prettier@3.6.2) @@ -395,6 +392,15 @@ importers: examples/mcp: {} + plugins/plugin-stagewise: + devDependencies: + '@neovate/code': + specifier: workspace:* + version: link:../.. + '@stagewise/agent-interface': + specifier: ^0.2.3 + version: 0.2.3 + vscode-extension: devDependencies: '@types/vscode': diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index f2f1d351..5805b456 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -2,6 +2,7 @@ packages: - browser - vscode-extension - examples/* + - plugins/* patchedDependencies: '@openai/agents-core': patches/@openai__agents-core.patch diff --git a/src/index.ts b/src/index.ts index fbb5c192..476bee0e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -24,6 +24,7 @@ export { createTool } from './tool'; export { z as _zod } from 'zod'; export { ConfigManager as _ConfigManager } from './config'; export { query as _query } from './query'; +export { Project as _Project } from './project'; export type { Plugin, Context };