mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-05-01 21:20:44 +00:00
feat(channels): add config validation, instructions, and sessionScope support
- Validate required fields (type, token) with clear error messages - Prepend channel instructions to first prompt of each session - SessionRouter respects sessionScope (user/thread/single) for routing keys
This commit is contained in:
parent
2985201317
commit
615ccd08f2
3 changed files with 67 additions and 14 deletions
|
|
@ -9,13 +9,14 @@ export abstract class ChannelBase {
|
|||
protected gate: SenderGate;
|
||||
protected router: SessionRouter;
|
||||
protected name: string;
|
||||
private instructedSessions: Set<string> = new Set();
|
||||
|
||||
constructor(name: string, config: ChannelConfig, bridge: AcpBridge) {
|
||||
this.name = name;
|
||||
this.config = config;
|
||||
this.bridge = bridge;
|
||||
this.gate = new SenderGate(config.senderPolicy, config.allowedUsers);
|
||||
this.router = new SessionRouter(bridge, config.cwd);
|
||||
this.router = new SessionRouter(bridge, config.cwd, config.sessionScope);
|
||||
}
|
||||
|
||||
abstract connect(): Promise<void>;
|
||||
|
|
@ -37,12 +38,19 @@ export abstract class ChannelBase {
|
|||
envelope.threadId,
|
||||
);
|
||||
|
||||
// Prepend channel instructions on first message of a session
|
||||
let promptText = envelope.text;
|
||||
if (this.config.instructions && !this.instructedSessions.has(sessionId)) {
|
||||
promptText = `${this.config.instructions}\n\n${envelope.text}`;
|
||||
this.instructedSessions.add(sessionId);
|
||||
}
|
||||
|
||||
console.log(
|
||||
`[Channel:${this.name}] Prompting session ${sessionId}: "${envelope.text.substring(0, 80)}"`,
|
||||
);
|
||||
|
||||
console.log(`[Channel:${this.name}] Waiting for prompt response...`);
|
||||
const response = await this.bridge.prompt(sessionId, envelope.text);
|
||||
const response = await this.bridge.prompt(sessionId, promptText);
|
||||
console.log(
|
||||
`[Channel:${this.name}] Got response (${response.length} chars): "${response.substring(0, 100)}"`,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import type { SessionTarget } from './types.js';
|
||||
import type { SessionScope, SessionTarget } from './types.js';
|
||||
import type { AcpBridge } from './AcpBridge.js';
|
||||
|
||||
export class SessionRouter {
|
||||
|
|
@ -7,10 +7,29 @@ export class SessionRouter {
|
|||
|
||||
private bridge: AcpBridge;
|
||||
private cwd: string;
|
||||
private scope: SessionScope;
|
||||
|
||||
constructor(bridge: AcpBridge, cwd: string) {
|
||||
constructor(bridge: AcpBridge, cwd: string, scope: SessionScope = 'user') {
|
||||
this.bridge = bridge;
|
||||
this.cwd = cwd;
|
||||
this.scope = scope;
|
||||
}
|
||||
|
||||
private routingKey(
|
||||
channelName: string,
|
||||
senderId: string,
|
||||
chatId: string,
|
||||
threadId?: string,
|
||||
): string {
|
||||
switch (this.scope) {
|
||||
case 'thread':
|
||||
return `${channelName}:${threadId || chatId}`;
|
||||
case 'single':
|
||||
return `${channelName}:__single__`;
|
||||
case 'user':
|
||||
default:
|
||||
return `${channelName}:${senderId}`;
|
||||
}
|
||||
}
|
||||
|
||||
async resolve(
|
||||
|
|
@ -19,7 +38,7 @@ export class SessionRouter {
|
|||
chatId: string,
|
||||
threadId?: string,
|
||||
): Promise<string> {
|
||||
const key = `${channelName}:${senderId}`;
|
||||
const key = this.routingKey(channelName, senderId, chatId, threadId);
|
||||
const existing = this.toSession.get(key);
|
||||
if (existing) {
|
||||
return existing;
|
||||
|
|
|
|||
|
|
@ -54,9 +54,42 @@ export const startCommand: CommandModule<object, { name: string }> = {
|
|||
}
|
||||
|
||||
const rawConfig = channels[name] as Record<string, unknown>;
|
||||
|
||||
// Validate required fields
|
||||
if (!rawConfig['type']) {
|
||||
writeStderrLine(
|
||||
`Error: Channel "${name}" is missing required field "type".`,
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
if (!rawConfig['token']) {
|
||||
writeStderrLine(
|
||||
`Error: Channel "${name}" is missing required field "token".`,
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const channelType = rawConfig['type'] as string;
|
||||
if (channelType !== 'telegram') {
|
||||
writeStderrLine(
|
||||
`Error: Channel type "${channelType}" is not yet supported. Only "telegram" is available.`,
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
let token: string;
|
||||
try {
|
||||
token = resolveEnvVars(rawConfig['token'] as string);
|
||||
} catch (err) {
|
||||
writeStderrLine(
|
||||
`Error: ${err instanceof Error ? err.message : String(err)}`,
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const config: ChannelConfig = {
|
||||
type: rawConfig['type'] as ChannelConfig['type'],
|
||||
token: resolveEnvVars(rawConfig['token'] as string),
|
||||
type: channelType as ChannelConfig['type'],
|
||||
token,
|
||||
senderPolicy:
|
||||
(rawConfig['senderPolicy'] as ChannelConfig['senderPolicy']) ||
|
||||
'allowlist',
|
||||
|
|
@ -68,13 +101,6 @@ export const startCommand: CommandModule<object, { name: string }> = {
|
|||
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})...`);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue