diff --git a/package-lock.json b/package-lock.json index 32aa31091..18bc0e6f0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18994,7 +18994,7 @@ "name": "@qwen-code/channel-plugin-example", "version": "0.1.0", "dependencies": { - "@qwen-code/channel-base": "file:../base", + "@qwen-code/channel-base": "^0.1.0", "ws": "^8.18.0" }, "devDependencies": { diff --git a/packages/channels/base/package.json b/packages/channels/base/package.json index 7513c43b4..e32304c66 100644 --- a/packages/channels/base/package.json +++ b/packages/channels/base/package.json @@ -3,10 +3,19 @@ "version": "0.1.0", "description": "Base channel infrastructure for Qwen Code", "type": "module", - "main": "src/index.ts", - "types": "src/index.ts", + "main": "dist/index.js", + "types": "dist/index.d.ts", "exports": { - ".": "./src/index.ts" + ".": { + "types": "./dist/index.d.ts", + "default": "./dist/index.js" + } + }, + "files": [ + "dist" + ], + "scripts": { + "build": "tsc --build" }, "dependencies": { "@agentclientprotocol/sdk": "^0.14.1" diff --git a/packages/channels/plugin-example/README.md b/packages/channels/plugin-example/README.md new file mode 100644 index 000000000..a814fdd54 --- /dev/null +++ b/packages/channels/plugin-example/README.md @@ -0,0 +1,97 @@ +# @qwen-code/channel-plugin-example + +A reference channel plugin for Qwen Code. It connects to a WebSocket server and routes messages through the full channel pipeline (access control, session routing, agent bridge). + +Use this package to: + +- **Try out the channel plugin system** — install it as an extension and run it with the built-in mock server +- **Use it as a starting point** — fork the source to build your own channel adapter (see the [Channel Plugin Developer Guide](../../docs/developers/channel-plugins.md)) + +## Quick start + +### 1. Install the package + +```bash +npm install @qwen-code/channel-plugin-example +``` + +### 2. Link it as a Qwen Code extension + +The package ships a `qwen-extension.json` manifest, so it works as an extension out of the box: + +```bash +qwen extensions link ./node_modules/@qwen-code/channel-plugin-example +``` + +### 3. Configure the channel + +Add a channel entry to `~/.qwen/settings.json`: + +```json +{ + "channels": { + "my-plugin-test": { + "type": "plugin-example", + "serverWsUrl": "ws://localhost:9201", + "senderPolicy": "open", + "sessionScope": "user", + "cwd": "/path/to/your/project" + } + } +} +``` + +### 4. Start the mock server + +```bash +npx qwen-channel-plugin-example-server +``` + +The server prints the HTTP and WebSocket URLs. You can customize ports with environment variables: + +```bash +HTTP_PORT=8080 WS_PORT=8081 npx qwen-channel-plugin-example-server +``` + +### 5. Start the channel + +In a separate terminal: + +```bash +qwen channel start my-plugin-test +``` + +### 6. Send a message + +```bash +curl -sX POST http://localhost:9200/message \ + -H 'Content-Type: application/json' \ + -d '{"senderId":"user1","senderName":"Tester","text":"What is 2+2?"}' +``` + +You should get a JSON response with the agent's reply. + +## How it works + +``` +Mock Server (HTTP + WS) + ↕ WebSocket +MockPluginChannel (this package) + → Envelope → ChannelBase.handleInbound() + → SenderGate → SessionRouter → AcpBridge.prompt() + → qwen-code agent → model API + ← response + ← sendMessage() → WebSocket → Mock Server + ← HTTP response +``` + +## Building your own channel + +See `src/MockPluginChannel.ts` for a working example. The key points: + +1. Extend `ChannelBase` and implement `connect()`, `sendMessage()`, `disconnect()` +2. Build an `Envelope` from incoming platform messages and call `this.handleInbound(envelope)` +3. Export a `plugin` object conforming to `ChannelPlugin` +4. Add a `qwen-extension.json` manifest + +Full guide: [Channel Plugin Developer Guide](../../docs/developers/channel-plugins.md) diff --git a/packages/channels/plugin-example/package.json b/packages/channels/plugin-example/package.json index d78bef775..9c59fd356 100644 --- a/packages/channels/plugin-example/package.json +++ b/packages/channels/plugin-example/package.json @@ -2,13 +2,27 @@ "name": "@qwen-code/channel-plugin-example", "version": "0.1.0", "type": "module", - "main": "src/index.ts", - "types": "src/index.ts", + "main": "dist/index.js", + "types": "dist/index.d.ts", "exports": { - ".": "./src/index.ts" + ".": { + "types": "./dist/index.d.ts", + "default": "./dist/index.js" + } + }, + "files": [ + "dist", + "src", + "qwen-extension.json" + ], + "bin": { + "qwen-channel-plugin-example-server": "dist/start-server.js" + }, + "scripts": { + "build": "tsc --build" }, "dependencies": { - "@qwen-code/channel-base": "file:../base", + "@qwen-code/channel-base": "^0.1.0", "ws": "^8.18.0" }, "devDependencies": { diff --git a/packages/channels/plugin-example/qwen-extension.json b/packages/channels/plugin-example/qwen-extension.json new file mode 100644 index 000000000..fdfff08ff --- /dev/null +++ b/packages/channels/plugin-example/qwen-extension.json @@ -0,0 +1,10 @@ +{ + "name": "qwen-channel-plugin-example", + "version": "0.1.0", + "channels": { + "plugin-example": { + "entry": "dist/index.js", + "displayName": "Plugin Example Channel" + } + } +} diff --git a/packages/channels/plugin-example/src/index.ts b/packages/channels/plugin-example/src/index.ts index 239fb95d6..d5734298e 100644 --- a/packages/channels/plugin-example/src/index.ts +++ b/packages/channels/plugin-example/src/index.ts @@ -12,5 +12,10 @@ export const plugin: ChannelPlugin = { displayName: 'Plugin Example', requiredConfigFields: ['serverWsUrl'], createChannel: (name, config, bridge, options) => - new MockPluginChannel(name, config as MockPluginConfig, bridge, options), + new MockPluginChannel( + name, + config as typeof config & { serverWsUrl: string }, + bridge, + options, + ), }; diff --git a/packages/channels/plugin-example/src/start-server.ts b/packages/channels/plugin-example/src/start-server.ts new file mode 100644 index 000000000..c033a1343 --- /dev/null +++ b/packages/channels/plugin-example/src/start-server.ts @@ -0,0 +1,36 @@ +#!/usr/bin/env node +/* eslint-disable no-console */ +/** + * Start the mock WebSocket server for testing the plugin-example channel. + * + * Usage: + * npx qwen-channel-plugin-example-server + * # or + * node node_modules/@qwen-code/channel-plugin-example/dist/start-server.js + * + * Environment variables: + * HTTP_PORT (default: 9200) + * WS_PORT (default: 9201) + */ +import { createMockServer } from './mock-server.js'; + +const httpPort = parseInt(process.env['HTTP_PORT'] || '9200', 10); +const wsPort = parseInt(process.env['WS_PORT'] || '9201', 10); + +const server = await createMockServer({ httpPort, wsPort }); + +console.log(`Mock server running:`); +console.log(` HTTP: http://localhost:${server.httpPort}`); +console.log(` WS: ws://localhost:${server.wsPort}`); +console.log(); +console.log(`Send a test message:`); +console.log(` curl -sX POST http://localhost:${server.httpPort}/message \\`); +console.log(` -H 'Content-Type: application/json' \\`); +console.log( + ` -d '{"senderId":"user1","senderName":"Tester","text":"Hello"}'`, +); + +process.on('SIGINT', async () => { + await server.close(); + process.exit(0); +});