mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-04-28 19:52:02 +00:00
Implements the channels infrastructure for connecting external messaging platforms to Qwen Code via ACP. Phase 1 supports plain text round-trip: Telegram user sends message -> AcpBridge -> qwen-code --acp -> response back to Telegram. New packages: - @qwen-code/channel-base: AcpBridge, SessionRouter, SenderGate, ChannelBase - @qwen-code/channel-telegram: TelegramAdapter using telegraf CLI: `qwen channel start <name>` reads from settings.json channels config, spawns ACP agent, connects to Telegram via polling.
107 lines
3.4 KiB
TypeScript
107 lines
3.4 KiB
TypeScript
import type { CommandModule } from 'yargs';
|
|
import { loadSettings } from '../../config/settings.js';
|
|
import { writeStderrLine, writeStdoutLine } from '../../utils/stdioHelpers.js';
|
|
import { AcpBridge } from '@qwen-code/channel-base';
|
|
import type { ChannelConfig } from '@qwen-code/channel-base';
|
|
import { TelegramChannel } from '@qwen-code/channel-telegram';
|
|
import * as path from 'node:path';
|
|
|
|
function resolveEnvVars(value: string): string {
|
|
if (value.startsWith('$')) {
|
|
const envName = value.substring(1);
|
|
const envValue = process.env[envName];
|
|
if (!envValue) {
|
|
throw new Error(
|
|
`Environment variable ${envName} is not set (referenced as ${value})`,
|
|
);
|
|
}
|
|
return envValue;
|
|
}
|
|
return value;
|
|
}
|
|
|
|
function findCliEntryPath(): string {
|
|
// When running from bundled dist/cli.js, use that same file for --acp
|
|
const mainModule = process.argv[1];
|
|
if (mainModule) {
|
|
return path.resolve(mainModule);
|
|
}
|
|
throw new Error('Cannot determine CLI entry path');
|
|
}
|
|
|
|
export const startCommand: CommandModule<object, { name: string }> = {
|
|
command: 'start <name>',
|
|
describe: 'Start a messaging channel',
|
|
builder: (yargs) =>
|
|
yargs.positional('name', {
|
|
type: 'string',
|
|
describe: 'Name of the channel (as configured in settings.json)',
|
|
demandOption: true,
|
|
}),
|
|
handler: async (argv) => {
|
|
const { name } = argv;
|
|
|
|
const settings = loadSettings(process.cwd());
|
|
const channels = (
|
|
settings.merged as unknown as { channels?: Record<string, unknown> }
|
|
).channels;
|
|
|
|
if (!channels || !channels[name]) {
|
|
writeStderrLine(
|
|
`Error: Channel "${name}" not found in settings. Add it to channels.${name} in settings.json.`,
|
|
);
|
|
process.exit(1);
|
|
}
|
|
|
|
const rawConfig = channels[name] as Record<string, unknown>;
|
|
const config: ChannelConfig = {
|
|
type: rawConfig['type'] as ChannelConfig['type'],
|
|
token: resolveEnvVars(rawConfig['token'] as string),
|
|
senderPolicy:
|
|
(rawConfig['senderPolicy'] as ChannelConfig['senderPolicy']) ||
|
|
'allowlist',
|
|
allowedUsers: (rawConfig['allowedUsers'] as string[]) || [],
|
|
sessionScope:
|
|
(rawConfig['sessionScope'] as ChannelConfig['sessionScope']) || 'user',
|
|
cwd: (rawConfig['cwd'] as string) || process.cwd(),
|
|
approvalMode: rawConfig['approvalMode'] as string | undefined,
|
|
instructions: rawConfig['instructions'] as string | undefined,
|
|
};
|
|
|
|
if (config.type !== 'telegram') {
|
|
writeStderrLine(
|
|
`Error: Channel type "${config.type}" is not yet supported. Only "telegram" is available.`,
|
|
);
|
|
process.exit(1);
|
|
}
|
|
|
|
const cliEntryPath = findCliEntryPath();
|
|
writeStdoutLine(`[Channel] CLI entry: ${cliEntryPath}`);
|
|
writeStdoutLine(`[Channel] Starting "${name}" (type=${config.type})...`);
|
|
|
|
const bridge = new AcpBridge({ cliEntryPath, cwd: config.cwd });
|
|
await bridge.start();
|
|
|
|
const channel = new TelegramChannel(name, config, bridge);
|
|
await channel.connect();
|
|
|
|
writeStdoutLine(`[Channel] "${name}" is running. Press Ctrl+C to stop.`);
|
|
|
|
// Keep process alive until interrupted
|
|
await new Promise<void>((resolve) => {
|
|
process.on('SIGINT', () => {
|
|
writeStdoutLine('\n[Channel] Shutting down...');
|
|
channel.disconnect();
|
|
bridge.stop();
|
|
resolve();
|
|
});
|
|
process.on('SIGTERM', () => {
|
|
channel.disconnect();
|
|
bridge.stop();
|
|
resolve();
|
|
});
|
|
});
|
|
|
|
process.exit(0);
|
|
},
|
|
};
|