feique 1.4.0 → 1.5.1
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.d.ts +1 -0
- package/dist/bridge/commands.js +32 -9
- 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 +42 -7
- 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
|
@@ -38,8 +38,13 @@ export function parseBridgeCommand(input) {
|
|
|
38
38
|
return parseSessionCommand(argument);
|
|
39
39
|
case '/admin':
|
|
40
40
|
return parseAdminCommand(argument);
|
|
41
|
-
case '/backend':
|
|
42
|
-
|
|
41
|
+
case '/backend': {
|
|
42
|
+
const arg = (argument || '').trim().toLowerCase();
|
|
43
|
+
if (arg === 'list' || arg === 'ls' || arg === '列表' || arg === '所有') {
|
|
44
|
+
return { kind: 'backend', action: 'list' };
|
|
45
|
+
}
|
|
46
|
+
return { kind: 'backend', name: arg || undefined };
|
|
47
|
+
}
|
|
43
48
|
case '/team':
|
|
44
49
|
return { kind: 'team' };
|
|
45
50
|
case '/learn':
|
|
@@ -85,7 +90,7 @@ export function buildHelpText() {
|
|
|
85
90
|
'/projects 查看可用项目',
|
|
86
91
|
'/project <名称> 切换项目',
|
|
87
92
|
'/status 查看当前状态',
|
|
88
|
-
'/backend codex|claude 切换后端',
|
|
93
|
+
'/backend codex|claude|qwen 切换后端',
|
|
89
94
|
'/team 查看团队成员活动',
|
|
90
95
|
'/learn <内容> 记录团队知识',
|
|
91
96
|
'/recall <关键词> 检索知识',
|
|
@@ -113,8 +118,10 @@ export function buildFullHelpText() {
|
|
|
113
118
|
'',
|
|
114
119
|
'后端切换',
|
|
115
120
|
'/backend 查看当前项目的活跃后端',
|
|
121
|
+
'/backend list 列出所有已注册后端 + fallback 链 + failover 开关',
|
|
116
122
|
'/backend codex 切换当前项目到 Codex 后端',
|
|
117
123
|
'/backend claude 切换当前项目到 Claude Code 后端',
|
|
124
|
+
'/backend qwen 切换当前项目到 Qwen Code 后端',
|
|
118
125
|
'',
|
|
119
126
|
'会话管理',
|
|
120
127
|
'/session list 列出当前项目保存过的会话',
|
|
@@ -283,6 +290,8 @@ export function describeBridgeCommand(command) {
|
|
|
283
290
|
}
|
|
284
291
|
return `管理员操作: ${command.resource} ${command.action}${'value' in command && command.value ? ` ${command.value}` : ''}`;
|
|
285
292
|
case 'backend':
|
|
293
|
+
if (command.action === 'list')
|
|
294
|
+
return '列出所有已注册后端';
|
|
286
295
|
return command.name ? `切换后端到 ${command.name}` : '查看当前后端';
|
|
287
296
|
case 'team':
|
|
288
297
|
return '查看团队协作态势';
|
|
@@ -337,7 +346,7 @@ export function isReadOnlyCommand(command) {
|
|
|
337
346
|
case 'session':
|
|
338
347
|
return command.action === 'list';
|
|
339
348
|
case 'backend':
|
|
340
|
-
return !command.name;
|
|
349
|
+
return command.action === 'list' || !command.name;
|
|
341
350
|
case 'team':
|
|
342
351
|
case 'recall':
|
|
343
352
|
case 'insights':
|
|
@@ -498,17 +507,17 @@ function parseNaturalLanguageCommand(input) {
|
|
|
498
507
|
}
|
|
499
508
|
// ── /backend: 后端切换(必须在项目切换之前,否则 "切到 claude" 会被误判为切换项目)──
|
|
500
509
|
// Pattern 1: 带"后端"关键词 — "切换后端到 claude" / "后端换成 codex"
|
|
501
|
-
const backendWithKeyword = normalized.match(/^(?:切换(?:到|为)?|使用|换(?:到|成)?|改(?:到|为|用)?)?\s*(?:后端(?:(?:切换)?(?:到|为)?)?)\s*(codex|claude)\s*(?:后端)?$/i);
|
|
510
|
+
const backendWithKeyword = normalized.match(/^(?:切换(?:到|为)?|使用|换(?:到|成)?|改(?:到|为|用)?)?\s*(?:后端(?:(?:切换)?(?:到|为)?)?)\s*(codex|claude|qwen)\s*(?:后端)?$/i);
|
|
502
511
|
if (backendWithKeyword) {
|
|
503
512
|
return { kind: 'backend', name: backendWithKeyword[1].toLowerCase() };
|
|
504
513
|
}
|
|
505
514
|
// Pattern 2: 不带"后端"— "用 claude" / "换成 codex" / "切到 claude" / "改用 codex"
|
|
506
|
-
const backendDirect = normalized.match(/^(?:用|使用|换(?:成|到)?|切(?:换?(?:到|成)?)?|改(?:用|成|到)?|转(?:到|成)?)\s*(codex|claude)(?:\s*(?:吧|看看|试试|帮我|来))?$/i);
|
|
515
|
+
const backendDirect = normalized.match(/^(?:用|使用|换(?:成|到)?|切(?:换?(?:到|成)?)?|改(?:用|成|到)?|转(?:到|成)?)\s*(codex|claude|qwen)(?:\s*(?:吧|看看|试试|帮我|来))?$/i);
|
|
507
516
|
if (backendDirect) {
|
|
508
517
|
return { kind: 'backend', name: backendDirect[1].toLowerCase() };
|
|
509
518
|
}
|
|
510
519
|
// Pattern 3: "codex/claude + 动词" — "claude 来" / "codex 帮我"
|
|
511
|
-
const backendNameFirst = normalized.match(/^(codex|claude)\s*(?:来(?:吧)?|帮我|试试|处理|干活|上)$/i);
|
|
520
|
+
const backendNameFirst = normalized.match(/^(codex|claude|qwen)\s*(?:来(?:吧)?|帮我|试试|处理|干活|上)$/i);
|
|
512
521
|
if (backendNameFirst) {
|
|
513
522
|
return { kind: 'backend', name: backendNameFirst[1].toLowerCase() };
|
|
514
523
|
}
|
|
@@ -520,13 +529,27 @@ function parseNaturalLanguageCommand(input) {
|
|
|
520
529
|
if (/^(?:现在|当前)?用的(?:什么|哪个)(?:后端|backend)?$/.test(normalized)) {
|
|
521
530
|
return { kind: 'backend' };
|
|
522
531
|
}
|
|
532
|
+
// Pattern 5b: 列出所有后端 / 有哪些后端 / 后端有哪些 / 支持哪些后端
|
|
533
|
+
if (/^(?:列出|列|查看|看|看看|显示|给我看|show|list)?\s*(?:所有|全部|已(?:注册|支持))?\s*(?:(?:可用|支持)(?:的)?)?\s*(?:后端|backend)s?$/i.test(normalized)
|
|
534
|
+
|| /^(?:有|支持)哪些(?:后端|backend)s?$/.test(normalized)
|
|
535
|
+
|| /^(?:后端|backend)s?\s*(?:有|列表|清单|列出)$/i.test(normalized)
|
|
536
|
+
|| /^(?:后端|backend)s?\s*有哪些$/i.test(normalized)
|
|
537
|
+
|| /^(?:list|show)\s+(?:all\s+)?backends?$/i.test(normalized)
|
|
538
|
+
|| /^(?:which|what)\s+backends?(?:\s+are\s+(?:available|supported))?\??$/i.test(normalized)) {
|
|
539
|
+
return { kind: 'backend', action: 'list' };
|
|
540
|
+
}
|
|
541
|
+
// Pattern 5c: 查看 fallback 链 / 降级顺序
|
|
542
|
+
if (/^(?:查看?|看|显示)?\s*(?:fallback|降级|备用|故障转移)\s*(?:链|顺序|列表|配置)?$/i.test(normalized)
|
|
543
|
+
|| /^fallback\s*chain\??$/i.test(normalized)) {
|
|
544
|
+
return { kind: 'backend', action: 'list' };
|
|
545
|
+
}
|
|
523
546
|
// 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);
|
|
547
|
+
const backendEnglish = normalized.match(/^(?:switch(?:\s+backend)?(?:\s+to)?|use|change(?:\s+backend)?(?:\s+to)?|backend)\s+(codex|claude|qwen)$/i);
|
|
525
548
|
if (backendEnglish) {
|
|
526
549
|
return { kind: 'backend', name: backendEnglish[1].toLowerCase() };
|
|
527
550
|
}
|
|
528
551
|
// Pattern 7: English name-first — "claude please" / "codex go"
|
|
529
|
-
const backendEnglishNameFirst = normalized.match(/^(codex|claude)\s+(?:please|go|now|backend)$/i);
|
|
552
|
+
const backendEnglishNameFirst = normalized.match(/^(codex|claude|qwen)\s+(?:please|go|now|backend)$/i);
|
|
530
553
|
if (backendEnglishNameFirst) {
|
|
531
554
|
return { kind: 'backend', name: backendEnglishNameFirst[1].toLowerCase() };
|
|
532
555
|
}
|