feique 1.4.0 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +2 -2
- package/README.md +2 -2
- package/dist/backend/claude.d.ts +2 -0
- package/dist/backend/claude.js +29 -0
- package/dist/backend/claude.js.map +1 -1
- package/dist/backend/codex.d.ts +2 -0
- package/dist/backend/codex.js +27 -0
- package/dist/backend/codex.js.map +1 -1
- package/dist/backend/factory.d.ts +23 -8
- package/dist/backend/factory.js +77 -58
- package/dist/backend/factory.js.map +1 -1
- package/dist/backend/probe.d.ts +0 -2
- package/dist/backend/probe.js +0 -14
- package/dist/backend/probe.js.map +1 -1
- package/dist/backend/qwen.d.ts +59 -0
- package/dist/backend/qwen.js +372 -0
- package/dist/backend/qwen.js.map +1 -0
- package/dist/backend/registry.d.ts +58 -0
- package/dist/backend/registry.js +23 -0
- package/dist/backend/registry.js.map +1 -0
- package/dist/backend/types.d.ts +6 -1
- package/dist/bridge/commands.js +7 -6
- package/dist/bridge/commands.js.map +1 -1
- package/dist/bridge/intent-classifier.d.ts +7 -2
- package/dist/bridge/intent-classifier.js +1 -1
- package/dist/bridge/intent-classifier.js.map +1 -1
- package/dist/bridge/run-pipeline.js +21 -10
- package/dist/bridge/run-pipeline.js.map +1 -1
- package/dist/bridge/service.js +5 -3
- package/dist/bridge/service.js.map +1 -1
- package/dist/config/schema.d.ts +47 -16
- package/dist/config/schema.js +38 -1
- package/dist/config/schema.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
import os from 'node:os';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import fs from 'node:fs/promises';
|
|
4
|
+
import { spawn } from 'node:child_process';
|
|
5
|
+
import { registerBackend } from './registry.js';
|
|
6
|
+
export class QwenBackend {
|
|
7
|
+
config;
|
|
8
|
+
qwenHomeDir;
|
|
9
|
+
name = 'qwen';
|
|
10
|
+
constructor(config, qwenHomeDir = resolveQwenHomeDir()) {
|
|
11
|
+
this.config = config;
|
|
12
|
+
this.qwenHomeDir = qwenHomeDir;
|
|
13
|
+
}
|
|
14
|
+
async run(options) {
|
|
15
|
+
const args = this.buildArgs(options);
|
|
16
|
+
const spawnSpec = this.buildSpawnSpec(args);
|
|
17
|
+
options.logger.info({ command: spawnSpec.command, args: spawnSpec.args, workdir: options.workdir }, 'Starting Qwen turn');
|
|
18
|
+
return await new Promise((resolve, reject) => {
|
|
19
|
+
const processRef = spawn(spawnSpec.command, spawnSpec.args, {
|
|
20
|
+
cwd: options.workdir,
|
|
21
|
+
env: {
|
|
22
|
+
...process.env,
|
|
23
|
+
NO_COLOR: '1',
|
|
24
|
+
},
|
|
25
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
26
|
+
});
|
|
27
|
+
let stderr = '';
|
|
28
|
+
let stdoutBuffer = '';
|
|
29
|
+
let sessionId = options.sessionId;
|
|
30
|
+
let finalMessage = '';
|
|
31
|
+
let inputTokens;
|
|
32
|
+
let outputTokens;
|
|
33
|
+
let settled = false;
|
|
34
|
+
let timeoutHandle;
|
|
35
|
+
let abortCleanup;
|
|
36
|
+
const finishReject = (error) => {
|
|
37
|
+
if (settled)
|
|
38
|
+
return;
|
|
39
|
+
settled = true;
|
|
40
|
+
cleanup();
|
|
41
|
+
reject(error);
|
|
42
|
+
};
|
|
43
|
+
const finishResolve = (result) => {
|
|
44
|
+
if (settled)
|
|
45
|
+
return;
|
|
46
|
+
settled = true;
|
|
47
|
+
cleanup();
|
|
48
|
+
resolve(result);
|
|
49
|
+
};
|
|
50
|
+
const cleanup = () => {
|
|
51
|
+
if (timeoutHandle)
|
|
52
|
+
clearTimeout(timeoutHandle);
|
|
53
|
+
abortCleanup?.();
|
|
54
|
+
abortCleanup = undefined;
|
|
55
|
+
};
|
|
56
|
+
const abortRun = (reason) => {
|
|
57
|
+
if (processRef.killed)
|
|
58
|
+
return;
|
|
59
|
+
const message = typeof reason === 'string' ? reason : reason instanceof Error ? reason.message : 'Qwen run aborted';
|
|
60
|
+
const error = new Error(message);
|
|
61
|
+
error.name = 'AbortError';
|
|
62
|
+
processRef.kill('SIGTERM');
|
|
63
|
+
setTimeout(() => {
|
|
64
|
+
if (!processRef.killed)
|
|
65
|
+
processRef.kill('SIGKILL');
|
|
66
|
+
}, 3000).unref();
|
|
67
|
+
finishReject(error);
|
|
68
|
+
};
|
|
69
|
+
if (options.timeoutMs && options.timeoutMs > 0) {
|
|
70
|
+
timeoutHandle = setTimeout(() => {
|
|
71
|
+
abortRun(`Qwen timed out after ${options.timeoutMs}ms`);
|
|
72
|
+
}, options.timeoutMs);
|
|
73
|
+
timeoutHandle.unref();
|
|
74
|
+
}
|
|
75
|
+
if (options.signal) {
|
|
76
|
+
const onAbort = () => abortRun(options.signal?.reason ?? 'Qwen run aborted');
|
|
77
|
+
if (options.signal.aborted) {
|
|
78
|
+
onAbort();
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
options.signal.addEventListener('abort', onAbort, { once: true });
|
|
82
|
+
abortCleanup = () => options.signal?.removeEventListener('abort', onAbort);
|
|
83
|
+
}
|
|
84
|
+
if (typeof processRef.pid === 'number') {
|
|
85
|
+
void options.onSpawn?.(processRef.pid);
|
|
86
|
+
}
|
|
87
|
+
processRef.stdout.on('data', async (chunk) => {
|
|
88
|
+
stdoutBuffer += chunk.toString('utf8');
|
|
89
|
+
const lines = stdoutBuffer.split(/\r?\n/);
|
|
90
|
+
stdoutBuffer = lines.pop() ?? '';
|
|
91
|
+
for (const line of lines) {
|
|
92
|
+
const trimmed = line.trim();
|
|
93
|
+
if (!trimmed.startsWith('{'))
|
|
94
|
+
continue;
|
|
95
|
+
try {
|
|
96
|
+
const event = JSON.parse(trimmed);
|
|
97
|
+
// Session id is emitted on both init and result events.
|
|
98
|
+
if (event.session_id) {
|
|
99
|
+
sessionId = event.session_id;
|
|
100
|
+
}
|
|
101
|
+
// Extract final text + usage from result event.
|
|
102
|
+
if (event.type === 'result') {
|
|
103
|
+
if (typeof event.result === 'string') {
|
|
104
|
+
finalMessage = event.result;
|
|
105
|
+
}
|
|
106
|
+
if (event.usage) {
|
|
107
|
+
if (typeof event.usage.input_tokens === 'number')
|
|
108
|
+
inputTokens = event.usage.input_tokens;
|
|
109
|
+
if (typeof event.usage.output_tokens === 'number')
|
|
110
|
+
outputTokens = event.usage.output_tokens;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
// Fall back to assistant text content if result event never arrives.
|
|
114
|
+
if (event.type === 'assistant' && event.message?.content) {
|
|
115
|
+
const texts = event.message.content
|
|
116
|
+
.filter(c => c.type === 'text' && typeof c.text === 'string')
|
|
117
|
+
.map(c => c.text)
|
|
118
|
+
.join('\n');
|
|
119
|
+
if (texts) {
|
|
120
|
+
finalMessage = texts;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
const backendEvent = {
|
|
124
|
+
type: event.type,
|
|
125
|
+
session_id: event.session_id ?? sessionId,
|
|
126
|
+
message: typeof event.result === 'string' ? event.result : undefined,
|
|
127
|
+
};
|
|
128
|
+
await options.onEvent?.(backendEvent);
|
|
129
|
+
}
|
|
130
|
+
catch {
|
|
131
|
+
options.logger.debug({ line }, 'Ignoring unparsable Qwen line');
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
processRef.stderr.on('data', (chunk) => {
|
|
136
|
+
stderr += chunk.toString('utf8');
|
|
137
|
+
});
|
|
138
|
+
processRef.on('error', (error) => {
|
|
139
|
+
finishReject(error instanceof Error ? error : new Error(String(error)));
|
|
140
|
+
});
|
|
141
|
+
processRef.on('close', (exitCode) => {
|
|
142
|
+
if (settled)
|
|
143
|
+
return;
|
|
144
|
+
if ((exitCode ?? 1) !== 0 && !finalMessage) {
|
|
145
|
+
finishReject(new Error(`Qwen exited with code ${exitCode ?? 1}: ${stderr.trim() || 'no stderr output'}`));
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
finishResolve({
|
|
149
|
+
sessionId,
|
|
150
|
+
finalMessage: finalMessage.trim(),
|
|
151
|
+
stderr: stderr.trim(),
|
|
152
|
+
exitCode: exitCode ?? 0,
|
|
153
|
+
inputTokens,
|
|
154
|
+
outputTokens,
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
summarizeEvent(event) {
|
|
160
|
+
if (event.type === 'error' && typeof event.message === 'string') {
|
|
161
|
+
return `Qwen 错误:${event.message}`;
|
|
162
|
+
}
|
|
163
|
+
return null;
|
|
164
|
+
}
|
|
165
|
+
async listProjectSessions(projectRoot, limit = 10) {
|
|
166
|
+
const sessions = await this.scanSessions();
|
|
167
|
+
const matches = [];
|
|
168
|
+
for (const session of sessions) {
|
|
169
|
+
const match = scoreSessionMatch(projectRoot, session.cwd);
|
|
170
|
+
if (!match)
|
|
171
|
+
continue;
|
|
172
|
+
matches.push({
|
|
173
|
+
...session,
|
|
174
|
+
matchKind: match.kind,
|
|
175
|
+
matchScore: match.score,
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
return matches
|
|
179
|
+
.sort((a, b) => {
|
|
180
|
+
const scoreDelta = (b.matchScore ?? 0) - (a.matchScore ?? 0);
|
|
181
|
+
if (scoreDelta !== 0)
|
|
182
|
+
return scoreDelta;
|
|
183
|
+
return b.updatedAt.localeCompare(a.updatedAt);
|
|
184
|
+
})
|
|
185
|
+
.slice(0, limit);
|
|
186
|
+
}
|
|
187
|
+
async findLatestSession(projectRoot) {
|
|
188
|
+
const [session] = await this.listProjectSessions(projectRoot, 1);
|
|
189
|
+
return session ?? null;
|
|
190
|
+
}
|
|
191
|
+
async findSessionById(projectRoot, sessionId) {
|
|
192
|
+
const sessions = await this.scanSessions();
|
|
193
|
+
const candidate = sessions.find(s => s.sessionId === sessionId);
|
|
194
|
+
if (!candidate)
|
|
195
|
+
return null;
|
|
196
|
+
const match = scoreSessionMatch(projectRoot, candidate.cwd);
|
|
197
|
+
if (!match)
|
|
198
|
+
return null;
|
|
199
|
+
return { ...candidate, matchKind: match.kind, matchScore: match.score };
|
|
200
|
+
}
|
|
201
|
+
buildArgs(options) {
|
|
202
|
+
const args = ['-p'];
|
|
203
|
+
args.push('--output-format', 'stream-json');
|
|
204
|
+
const approvalMode = options.projectConfig?.approvalMode ?? this.config.defaultApprovalMode;
|
|
205
|
+
args.push('--approval-mode', approvalMode);
|
|
206
|
+
const model = options.projectConfig?.model ?? this.config.defaultModel;
|
|
207
|
+
if (model) {
|
|
208
|
+
args.push('--model', model);
|
|
209
|
+
}
|
|
210
|
+
const allowedTools = options.projectConfig?.allowedTools ?? this.config.allowedTools;
|
|
211
|
+
if (allowedTools && allowedTools.length > 0) {
|
|
212
|
+
args.push('--allowed-tools', ...allowedTools);
|
|
213
|
+
}
|
|
214
|
+
const systemPromptAppend = options.projectConfig?.systemPromptAppend ?? this.config.systemPromptAppend;
|
|
215
|
+
if (systemPromptAppend) {
|
|
216
|
+
args.push('--append-system-prompt', systemPromptAppend);
|
|
217
|
+
}
|
|
218
|
+
if (options.sessionId) {
|
|
219
|
+
args.push('--resume', options.sessionId);
|
|
220
|
+
}
|
|
221
|
+
args.push(options.prompt);
|
|
222
|
+
return args;
|
|
223
|
+
}
|
|
224
|
+
buildSpawnSpec(qwenArgs) {
|
|
225
|
+
if (!this.config.preExec) {
|
|
226
|
+
return { command: this.config.bin, args: qwenArgs };
|
|
227
|
+
}
|
|
228
|
+
const shell = this.config.shell ?? process.env.SHELL ?? '/bin/zsh';
|
|
229
|
+
const chainedCommand = `${this.config.preExec} && ${quoteShellCommand([this.config.bin, ...qwenArgs])}`;
|
|
230
|
+
return { command: shell, args: ['-ic', chainedCommand] };
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Walk `~/.qwen/projects/<slug>/chats/*.jsonl` and parse the first
|
|
234
|
+
* line of each file to extract `sessionId` + `cwd` + `timestamp`.
|
|
235
|
+
*
|
|
236
|
+
* Qwen encodes the project directory as the slug (e.g. `/Users/dh` →
|
|
237
|
+
* `-Users-dh`). We don't depend on that mapping — we walk every slug
|
|
238
|
+
* and match by parsed `cwd`.
|
|
239
|
+
*/
|
|
240
|
+
async scanSessions() {
|
|
241
|
+
const projectsDir = path.join(this.qwenHomeDir, 'projects');
|
|
242
|
+
const sessions = new Map();
|
|
243
|
+
let slugEntries;
|
|
244
|
+
try {
|
|
245
|
+
slugEntries = await fs.readdir(projectsDir);
|
|
246
|
+
}
|
|
247
|
+
catch {
|
|
248
|
+
return [];
|
|
249
|
+
}
|
|
250
|
+
for (const slug of slugEntries) {
|
|
251
|
+
const chatsDir = path.join(projectsDir, slug, 'chats');
|
|
252
|
+
const chatFiles = await fs.readdir(chatsDir).catch(() => []);
|
|
253
|
+
for (const chatFile of chatFiles) {
|
|
254
|
+
if (!chatFile.endsWith('.jsonl'))
|
|
255
|
+
continue;
|
|
256
|
+
const filePath = path.join(chatsDir, chatFile);
|
|
257
|
+
const stat = await fs.stat(filePath).catch(() => null);
|
|
258
|
+
if (!stat?.isFile())
|
|
259
|
+
continue;
|
|
260
|
+
try {
|
|
261
|
+
const content = await fs.readFile(filePath, 'utf8');
|
|
262
|
+
const firstLine = content.split(/\r?\n/, 1)[0]?.trim();
|
|
263
|
+
if (!firstLine)
|
|
264
|
+
continue;
|
|
265
|
+
const parsed = JSON.parse(firstLine);
|
|
266
|
+
const sessionId = parsed.sessionId;
|
|
267
|
+
const cwd = parsed.cwd;
|
|
268
|
+
if (!sessionId || !cwd)
|
|
269
|
+
continue;
|
|
270
|
+
const createdAt = parsed.timestamp;
|
|
271
|
+
const updatedAt = new Date(stat.mtimeMs).toISOString();
|
|
272
|
+
const existing = sessions.get(sessionId);
|
|
273
|
+
if (!existing || existing.updatedAt < updatedAt) {
|
|
274
|
+
const record = {
|
|
275
|
+
sessionId,
|
|
276
|
+
cwd,
|
|
277
|
+
updatedAt,
|
|
278
|
+
filePath,
|
|
279
|
+
source: 'sessions',
|
|
280
|
+
backend: 'qwen',
|
|
281
|
+
};
|
|
282
|
+
if (createdAt) {
|
|
283
|
+
record.createdAt = createdAt;
|
|
284
|
+
}
|
|
285
|
+
sessions.set(sessionId, record);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
catch {
|
|
289
|
+
continue;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
return [...sessions.values()].sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
function resolveQwenHomeDir() {
|
|
297
|
+
const configured = process.env.QWEN_HOME?.trim();
|
|
298
|
+
if (!configured)
|
|
299
|
+
return path.join(os.homedir(), '.qwen');
|
|
300
|
+
if (configured === '~')
|
|
301
|
+
return os.homedir();
|
|
302
|
+
if (configured.startsWith('~/'))
|
|
303
|
+
return path.join(os.homedir(), configured.slice(2));
|
|
304
|
+
return path.resolve(configured);
|
|
305
|
+
}
|
|
306
|
+
function quoteShellArg(value) {
|
|
307
|
+
if (value.length === 0)
|
|
308
|
+
return "''";
|
|
309
|
+
return `'${value.replace(/'/g, `'"'"'`)}'`;
|
|
310
|
+
}
|
|
311
|
+
function quoteShellCommand(parts) {
|
|
312
|
+
return parts.map(quoteShellArg).join(' ');
|
|
313
|
+
}
|
|
314
|
+
const FUZZY_SUFFIX_TOKENS = new Set(['bridge', 'repo', 'project', 'workspace']);
|
|
315
|
+
function scoreSessionMatch(projectRoot, sessionCwd) {
|
|
316
|
+
const normalizedProjectRoot = path.resolve(projectRoot).replace(/\/+$/, '').toLowerCase();
|
|
317
|
+
const normalizedSessionRoot = path.resolve(sessionCwd).replace(/\/+$/, '').toLowerCase();
|
|
318
|
+
if (normalizedProjectRoot === normalizedSessionRoot) {
|
|
319
|
+
return { kind: 'exact-root', score: 100 };
|
|
320
|
+
}
|
|
321
|
+
const projectBase = path.basename(normalizedProjectRoot);
|
|
322
|
+
const sessionBase = path.basename(normalizedSessionRoot);
|
|
323
|
+
if (projectBase === sessionBase) {
|
|
324
|
+
return { kind: 'basename', score: 80 };
|
|
325
|
+
}
|
|
326
|
+
const normalizedProjectName = normalizeProjectName(projectBase);
|
|
327
|
+
const normalizedSessionName = normalizeProjectName(sessionBase);
|
|
328
|
+
if (normalizedProjectName && normalizedProjectName === normalizedSessionName) {
|
|
329
|
+
return { kind: 'normalized-name', score: 60 };
|
|
330
|
+
}
|
|
331
|
+
if (normalizedProjectName.length >= 5 && normalizedSessionName.includes(normalizedProjectName)) {
|
|
332
|
+
return { kind: 'basename-contains', score: 40 };
|
|
333
|
+
}
|
|
334
|
+
return null;
|
|
335
|
+
}
|
|
336
|
+
function normalizeProjectName(input) {
|
|
337
|
+
const tokens = input
|
|
338
|
+
.toLowerCase()
|
|
339
|
+
.replace(/\.git$/, '')
|
|
340
|
+
.split(/[^a-z0-9]+/)
|
|
341
|
+
.filter(Boolean);
|
|
342
|
+
const filtered = tokens.filter(token => !FUZZY_SUFFIX_TOKENS.has(token));
|
|
343
|
+
return (filtered.length > 0 ? filtered : tokens).join('-');
|
|
344
|
+
}
|
|
345
|
+
// ---------------------------------------------------------------------------
|
|
346
|
+
// Registry definition
|
|
347
|
+
// ---------------------------------------------------------------------------
|
|
348
|
+
export const qwenBackendDefinition = {
|
|
349
|
+
name: 'qwen',
|
|
350
|
+
create(config) {
|
|
351
|
+
return new QwenBackend({
|
|
352
|
+
bin: config.qwen?.bin ?? 'qwen',
|
|
353
|
+
shell: config.qwen?.shell ?? config.codex.shell,
|
|
354
|
+
preExec: config.qwen?.pre_exec ?? config.codex.pre_exec,
|
|
355
|
+
defaultApprovalMode: config.qwen?.default_approval_mode ?? 'default',
|
|
356
|
+
defaultModel: config.qwen?.default_model,
|
|
357
|
+
allowedTools: config.qwen?.allowed_tools,
|
|
358
|
+
systemPromptAppend: config.qwen?.system_prompt_append,
|
|
359
|
+
runTimeoutMs: config.qwen?.run_timeout_ms ?? config.codex.run_timeout_ms,
|
|
360
|
+
});
|
|
361
|
+
},
|
|
362
|
+
probeSpec(config) {
|
|
363
|
+
return {
|
|
364
|
+
bin: config.qwen?.bin ?? 'qwen',
|
|
365
|
+
shell: config.qwen?.shell ?? config.codex.shell,
|
|
366
|
+
preExec: config.qwen?.pre_exec ?? config.codex.pre_exec,
|
|
367
|
+
};
|
|
368
|
+
},
|
|
369
|
+
defaultFallback: ['claude', 'codex'],
|
|
370
|
+
};
|
|
371
|
+
registerBackend(qwenBackendDefinition);
|
|
372
|
+
//# sourceMappingURL=qwen.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"qwen.js","sourceRoot":"","sources":["../../src/backend/qwen.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import type { BridgeConfig } from '../config/schema.js';
|
|
2
|
+
import type { Backend, BackendName } from './types.js';
|
|
3
|
+
import type { ProbeSpec } from './probe.js';
|
|
4
|
+
import type { CodexSessionIndex } from '../codex/session-index.js';
|
|
5
|
+
/**
|
|
6
|
+
* Extensible backend registry.
|
|
7
|
+
*
|
|
8
|
+
* Every backend (codex, claude, qwen, ...) contributes a BackendDefinition
|
|
9
|
+
* that knows how to construct itself from BridgeConfig and how to expose
|
|
10
|
+
* its probe spec for startup failover. Adding a new backend is purely an
|
|
11
|
+
* additive change: drop a new file under src/backend/, export its
|
|
12
|
+
* definition, call registerBackend() at module load, and wire it into
|
|
13
|
+
* src/backend/factory.ts's side-effect import list.
|
|
14
|
+
*
|
|
15
|
+
* BackendName is intentionally widened to `string` (see types.ts) —
|
|
16
|
+
* compile-time literal unions were incompatible with a registry because
|
|
17
|
+
* every new backend would require touching the type. Runtime validation
|
|
18
|
+
* happens in requireBackendDefinition().
|
|
19
|
+
*/
|
|
20
|
+
export interface BackendDependencies {
|
|
21
|
+
/**
|
|
22
|
+
* Shared CodexSessionIndex, passed in by FeiqueService. Any backend
|
|
23
|
+
* that wants access can read it from here; codex relies on it for
|
|
24
|
+
* resume semantics.
|
|
25
|
+
*/
|
|
26
|
+
codexSessionIndex?: CodexSessionIndex;
|
|
27
|
+
}
|
|
28
|
+
export interface BackendDefinition {
|
|
29
|
+
/** Unique registry key, e.g. 'codex', 'claude', 'qwen'. */
|
|
30
|
+
readonly name: string;
|
|
31
|
+
/**
|
|
32
|
+
* Build a live Backend instance from config. Called on every run so
|
|
33
|
+
* hot-reloaded config is picked up immediately.
|
|
34
|
+
*/
|
|
35
|
+
create(config: BridgeConfig, deps: BackendDependencies): Backend;
|
|
36
|
+
/**
|
|
37
|
+
* Extract the probe spec (bin + shell + pre_exec) from config.
|
|
38
|
+
* Used by the failover resolver before each run.
|
|
39
|
+
*/
|
|
40
|
+
probeSpec(config: BridgeConfig): ProbeSpec;
|
|
41
|
+
/**
|
|
42
|
+
* Optional default fallback chain. Used when neither the project nor
|
|
43
|
+
* the global config supplies an explicit fallback list. If omitted,
|
|
44
|
+
* the resolver falls back to "every other registered backend" in
|
|
45
|
+
* registration order.
|
|
46
|
+
*
|
|
47
|
+
* Example: codex's default fallback is ['claude'] so that when codex
|
|
48
|
+
* is broken on a box, users get claude automatically without needing
|
|
49
|
+
* to set anything.
|
|
50
|
+
*/
|
|
51
|
+
readonly defaultFallback?: readonly string[];
|
|
52
|
+
}
|
|
53
|
+
export declare function registerBackend(definition: BackendDefinition): void;
|
|
54
|
+
export declare function getBackendDefinition(name: string): BackendDefinition | undefined;
|
|
55
|
+
export declare function requireBackendDefinition(name: string): BackendDefinition;
|
|
56
|
+
export declare function listBackendNames(): BackendName[];
|
|
57
|
+
/** Visible for tests only. */
|
|
58
|
+
export declare function clearBackendRegistry(): void;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
const registry = new Map();
|
|
2
|
+
export function registerBackend(definition) {
|
|
3
|
+
registry.set(definition.name, definition);
|
|
4
|
+
}
|
|
5
|
+
export function getBackendDefinition(name) {
|
|
6
|
+
return registry.get(name);
|
|
7
|
+
}
|
|
8
|
+
export function requireBackendDefinition(name) {
|
|
9
|
+
const def = registry.get(name);
|
|
10
|
+
if (!def) {
|
|
11
|
+
const known = [...registry.keys()].join(', ') || '(none)';
|
|
12
|
+
throw new Error(`Unknown backend: ${name}. Registered backends: ${known}`);
|
|
13
|
+
}
|
|
14
|
+
return def;
|
|
15
|
+
}
|
|
16
|
+
export function listBackendNames() {
|
|
17
|
+
return [...registry.keys()];
|
|
18
|
+
}
|
|
19
|
+
/** Visible for tests only. */
|
|
20
|
+
export function clearBackendRegistry() {
|
|
21
|
+
registry.clear();
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=registry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry.js","sourceRoot":"","sources":["../../src/backend/registry.ts"],"names":[],"mappings":"AA2DA,MAAM,QAAQ,GAAG,IAAI,GAAG,EAA6B,CAAC;AAEtD,MAAM,UAAU,eAAe,CAAC,UAA6B;IAC3D,QAAQ,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC;AAC5C,CAAC;AAED,MAAM,UAAU,oBAAoB,CAAC,IAAY;IAC/C,OAAO,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;AAC5B,CAAC;AAED,MAAM,UAAU,wBAAwB,CAAC,IAAY;IACnD,MAAM,GAAG,GAAG,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAC/B,IAAI,CAAC,GAAG,EAAE,CAAC;QACT,MAAM,KAAK,GAAG,CAAC,GAAG,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,QAAQ,CAAC;QAC1D,MAAM,IAAI,KAAK,CAAC,oBAAoB,IAAI,0BAA0B,KAAK,EAAE,CAAC,CAAC;IAC7E,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,MAAM,UAAU,gBAAgB;IAC9B,OAAO,CAAC,GAAG,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC;AAC9B,CAAC;AAED,8BAA8B;AAC9B,MAAM,UAAU,oBAAoB;IAClC,QAAQ,CAAC,KAAK,EAAE,CAAC;AACnB,CAAC"}
|
package/dist/backend/types.d.ts
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import type { Logger } from '../logging.js';
|
|
2
|
-
|
|
2
|
+
/**
|
|
3
|
+
* Backend identifier. Intentionally typed as `string` (not a literal union)
|
|
4
|
+
* because backends are registered at runtime via src/backend/registry.ts.
|
|
5
|
+
* Use requireBackendDefinition() to validate at the boundary.
|
|
6
|
+
*/
|
|
7
|
+
export type BackendName = string;
|
|
3
8
|
export interface BackendEvent {
|
|
4
9
|
type?: string;
|
|
5
10
|
session_id?: string;
|
package/dist/bridge/commands.js
CHANGED
|
@@ -85,7 +85,7 @@ export function buildHelpText() {
|
|
|
85
85
|
'/projects 查看可用项目',
|
|
86
86
|
'/project <名称> 切换项目',
|
|
87
87
|
'/status 查看当前状态',
|
|
88
|
-
'/backend codex|claude 切换后端',
|
|
88
|
+
'/backend codex|claude|qwen 切换后端',
|
|
89
89
|
'/team 查看团队成员活动',
|
|
90
90
|
'/learn <内容> 记录团队知识',
|
|
91
91
|
'/recall <关键词> 检索知识',
|
|
@@ -115,6 +115,7 @@ export function buildFullHelpText() {
|
|
|
115
115
|
'/backend 查看当前项目的活跃后端',
|
|
116
116
|
'/backend codex 切换当前项目到 Codex 后端',
|
|
117
117
|
'/backend claude 切换当前项目到 Claude Code 后端',
|
|
118
|
+
'/backend qwen 切换当前项目到 Qwen Code 后端',
|
|
118
119
|
'',
|
|
119
120
|
'会话管理',
|
|
120
121
|
'/session list 列出当前项目保存过的会话',
|
|
@@ -498,17 +499,17 @@ function parseNaturalLanguageCommand(input) {
|
|
|
498
499
|
}
|
|
499
500
|
// ── /backend: 后端切换(必须在项目切换之前,否则 "切到 claude" 会被误判为切换项目)──
|
|
500
501
|
// Pattern 1: 带"后端"关键词 — "切换后端到 claude" / "后端换成 codex"
|
|
501
|
-
const backendWithKeyword = normalized.match(/^(?:切换(?:到|为)?|使用|换(?:到|成)?|改(?:到|为|用)?)?\s*(?:后端(?:(?:切换)?(?:到|为)?)?)\s*(codex|claude)\s*(?:后端)?$/i);
|
|
502
|
+
const backendWithKeyword = normalized.match(/^(?:切换(?:到|为)?|使用|换(?:到|成)?|改(?:到|为|用)?)?\s*(?:后端(?:(?:切换)?(?:到|为)?)?)\s*(codex|claude|qwen)\s*(?:后端)?$/i);
|
|
502
503
|
if (backendWithKeyword) {
|
|
503
504
|
return { kind: 'backend', name: backendWithKeyword[1].toLowerCase() };
|
|
504
505
|
}
|
|
505
506
|
// Pattern 2: 不带"后端"— "用 claude" / "换成 codex" / "切到 claude" / "改用 codex"
|
|
506
|
-
const backendDirect = normalized.match(/^(?:用|使用|换(?:成|到)?|切(?:换?(?:到|成)?)?|改(?:用|成|到)?|转(?:到|成)?)\s*(codex|claude)(?:\s*(?:吧|看看|试试|帮我|来))?$/i);
|
|
507
|
+
const backendDirect = normalized.match(/^(?:用|使用|换(?:成|到)?|切(?:换?(?:到|成)?)?|改(?:用|成|到)?|转(?:到|成)?)\s*(codex|claude|qwen)(?:\s*(?:吧|看看|试试|帮我|来))?$/i);
|
|
507
508
|
if (backendDirect) {
|
|
508
509
|
return { kind: 'backend', name: backendDirect[1].toLowerCase() };
|
|
509
510
|
}
|
|
510
511
|
// Pattern 3: "codex/claude + 动词" — "claude 来" / "codex 帮我"
|
|
511
|
-
const backendNameFirst = normalized.match(/^(codex|claude)\s*(?:来(?:吧)?|帮我|试试|处理|干活|上)$/i);
|
|
512
|
+
const backendNameFirst = normalized.match(/^(codex|claude|qwen)\s*(?:来(?:吧)?|帮我|试试|处理|干活|上)$/i);
|
|
512
513
|
if (backendNameFirst) {
|
|
513
514
|
return { kind: 'backend', name: backendNameFirst[1].toLowerCase() };
|
|
514
515
|
}
|
|
@@ -521,12 +522,12 @@ function parseNaturalLanguageCommand(input) {
|
|
|
521
522
|
return { kind: 'backend' };
|
|
522
523
|
}
|
|
523
524
|
// Pattern 6: English — "switch to claude" / "use codex" / "change to claude"
|
|
524
|
-
const backendEnglish = normalized.match(/^(?:switch(?:\s+backend)?(?:\s+to)?|use|change(?:\s+backend)?(?:\s+to)?|backend)\s+(codex|claude)$/i);
|
|
525
|
+
const backendEnglish = normalized.match(/^(?:switch(?:\s+backend)?(?:\s+to)?|use|change(?:\s+backend)?(?:\s+to)?|backend)\s+(codex|claude|qwen)$/i);
|
|
525
526
|
if (backendEnglish) {
|
|
526
527
|
return { kind: 'backend', name: backendEnglish[1].toLowerCase() };
|
|
527
528
|
}
|
|
528
529
|
// Pattern 7: English name-first — "claude please" / "codex go"
|
|
529
|
-
const backendEnglishNameFirst = normalized.match(/^(codex|claude)\s+(?:please|go|now|backend)$/i);
|
|
530
|
+
const backendEnglishNameFirst = normalized.match(/^(codex|claude|qwen)\s+(?:please|go|now|backend)$/i);
|
|
530
531
|
if (backendEnglishNameFirst) {
|
|
531
532
|
return { kind: 'backend', name: backendEnglishNameFirst[1].toLowerCase() };
|
|
532
533
|
}
|