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 gate: SenderGate;
|
||||||
protected router: SessionRouter;
|
protected router: SessionRouter;
|
||||||
protected name: string;
|
protected name: string;
|
||||||
|
private instructedSessions: Set<string> = new Set();
|
||||||
|
|
||||||
constructor(name: string, config: ChannelConfig, bridge: AcpBridge) {
|
constructor(name: string, config: ChannelConfig, bridge: AcpBridge) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.config = config;
|
this.config = config;
|
||||||
this.bridge = bridge;
|
this.bridge = bridge;
|
||||||
this.gate = new SenderGate(config.senderPolicy, config.allowedUsers);
|
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>;
|
abstract connect(): Promise<void>;
|
||||||
|
|
@ -37,12 +38,19 @@ export abstract class ChannelBase {
|
||||||
envelope.threadId,
|
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(
|
console.log(
|
||||||
`[Channel:${this.name}] Prompting session ${sessionId}: "${envelope.text.substring(0, 80)}"`,
|
`[Channel:${this.name}] Prompting session ${sessionId}: "${envelope.text.substring(0, 80)}"`,
|
||||||
);
|
);
|
||||||
|
|
||||||
console.log(`[Channel:${this.name}] Waiting for prompt response...`);
|
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(
|
console.log(
|
||||||
`[Channel:${this.name}] Got response (${response.length} chars): "${response.substring(0, 100)}"`,
|
`[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';
|
import type { AcpBridge } from './AcpBridge.js';
|
||||||
|
|
||||||
export class SessionRouter {
|
export class SessionRouter {
|
||||||
|
|
@ -7,10 +7,29 @@ export class SessionRouter {
|
||||||
|
|
||||||
private bridge: AcpBridge;
|
private bridge: AcpBridge;
|
||||||
private cwd: string;
|
private cwd: string;
|
||||||
|
private scope: SessionScope;
|
||||||
|
|
||||||
constructor(bridge: AcpBridge, cwd: string) {
|
constructor(bridge: AcpBridge, cwd: string, scope: SessionScope = 'user') {
|
||||||
this.bridge = bridge;
|
this.bridge = bridge;
|
||||||
this.cwd = cwd;
|
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(
|
async resolve(
|
||||||
|
|
@ -19,7 +38,7 @@ export class SessionRouter {
|
||||||
chatId: string,
|
chatId: string,
|
||||||
threadId?: string,
|
threadId?: string,
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
const key = `${channelName}:${senderId}`;
|
const key = this.routingKey(channelName, senderId, chatId, threadId);
|
||||||
const existing = this.toSession.get(key);
|
const existing = this.toSession.get(key);
|
||||||
if (existing) {
|
if (existing) {
|
||||||
return existing;
|
return existing;
|
||||||
|
|
|
||||||
|
|
@ -54,9 +54,42 @@ export const startCommand: CommandModule<object, { name: string }> = {
|
||||||
}
|
}
|
||||||
|
|
||||||
const rawConfig = channels[name] as Record<string, unknown>;
|
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 = {
|
const config: ChannelConfig = {
|
||||||
type: rawConfig['type'] as ChannelConfig['type'],
|
type: channelType as ChannelConfig['type'],
|
||||||
token: resolveEnvVars(rawConfig['token'] as string),
|
token,
|
||||||
senderPolicy:
|
senderPolicy:
|
||||||
(rawConfig['senderPolicy'] as ChannelConfig['senderPolicy']) ||
|
(rawConfig['senderPolicy'] as ChannelConfig['senderPolicy']) ||
|
||||||
'allowlist',
|
'allowlist',
|
||||||
|
|
@ -68,13 +101,6 @@ export const startCommand: CommandModule<object, { name: string }> = {
|
||||||
instructions: rawConfig['instructions'] 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();
|
const cliEntryPath = findCliEntryPath();
|
||||||
writeStdoutLine(`[Channel] CLI entry: ${cliEntryPath}`);
|
writeStdoutLine(`[Channel] CLI entry: ${cliEntryPath}`);
|
||||||
writeStdoutLine(`[Channel] Starting "${name}" (type=${config.type})...`);
|
writeStdoutLine(`[Channel] Starting "${name}" (type=${config.type})...`);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue