agent-mp 0.4.5 → 0.4.7
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/dist/commands/repl.js +16 -0
- package/dist/core/engine.js +69 -31
- package/dist/utils/qwen-auth.d.ts +4 -8
- package/dist/utils/qwen-auth.js +50 -67
- package/package.json +3 -3
package/dist/commands/repl.js
CHANGED
|
@@ -720,6 +720,8 @@ function cmdHelp(fi) {
|
|
|
720
720
|
{ key: '/run orch <task>', value: 'Run only orchestrator' },
|
|
721
721
|
{ key: '/run impl <id>', value: 'Run only implementor' },
|
|
722
722
|
{ key: '/run rev <id>', value: 'Run only reviewer' },
|
|
723
|
+
{ key: '/run explorer [task]', value: 'Run only explorer' },
|
|
724
|
+
{ key: '/explorer [task]', value: 'Run explorer (shortcut)' },
|
|
723
725
|
{ key: '/models', value: 'List models for all installed CLIs' },
|
|
724
726
|
{ key: '/models <cli>', value: 'List models for a specific CLI' },
|
|
725
727
|
{ key: '/login', value: 'Login (Qwen OAuth or CLI auth)' },
|
|
@@ -1035,6 +1037,11 @@ export async function runRepl(resumeSession) {
|
|
|
1035
1037
|
const progress = await readJson(path.join(taskDir, 'progress.json'));
|
|
1036
1038
|
await engine.runReviewer(taskId, plan, progress);
|
|
1037
1039
|
}
|
|
1040
|
+
else if (args[0] === 'exp' || args[0] === 'explorer') {
|
|
1041
|
+
const task = args.slice(1).join(' ');
|
|
1042
|
+
const engine = new AgentEngine(config, dir, gCoordinatorCmd, rl, fi, handleCmd);
|
|
1043
|
+
await engine.runExplorer(task);
|
|
1044
|
+
}
|
|
1038
1045
|
else {
|
|
1039
1046
|
const task = args.join(' ');
|
|
1040
1047
|
const engine = new AgentEngine(config, dir, gCoordinatorCmd, rl, fi, handleCmd);
|
|
@@ -1042,6 +1049,15 @@ export async function runRepl(resumeSession) {
|
|
|
1042
1049
|
}
|
|
1043
1050
|
break;
|
|
1044
1051
|
}
|
|
1052
|
+
case 'explorer':
|
|
1053
|
+
case 'exp': {
|
|
1054
|
+
const dir = process.cwd();
|
|
1055
|
+
const config = await loadProjectConfig(dir);
|
|
1056
|
+
const task = args.join(' ');
|
|
1057
|
+
const engine = new AgentEngine(config, dir, gCoordinatorCmd, rl, fi, handleCmd);
|
|
1058
|
+
await engine.runExplorer(task);
|
|
1059
|
+
break;
|
|
1060
|
+
}
|
|
1045
1061
|
case 'models':
|
|
1046
1062
|
case 'model':
|
|
1047
1063
|
await withRl((rl) => cmdModels(args[0], fi, rl));
|
package/dist/core/engine.js
CHANGED
|
@@ -88,7 +88,7 @@ async function ask(prompt, rl, fi) {
|
|
|
88
88
|
const tempRl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
89
89
|
return new Promise((resolve) => tempRl.question(prompt, (a) => { tempRl.close(); resolve(a); }));
|
|
90
90
|
}
|
|
91
|
-
function runCli(cmd, prompt, timeoutMs = 600000, envOverride) {
|
|
91
|
+
function runCli(cmd, prompt, timeoutMs = 600000, envOverride, onData) {
|
|
92
92
|
// Si el comando es "qwen-direct", usar la API directa sin CLI
|
|
93
93
|
if (cmd.startsWith('qwen-direct')) {
|
|
94
94
|
const model = cmd.includes('-m') ? cmd.split('-m')[1]?.trim().split(/\s/)[0] : 'coder-model';
|
|
@@ -143,7 +143,7 @@ function runCli(cmd, prompt, timeoutMs = 600000, envOverride) {
|
|
|
143
143
|
});
|
|
144
144
|
let output = '';
|
|
145
145
|
let stderr = '';
|
|
146
|
-
child.stdout?.on('data', (d) => {
|
|
146
|
+
child.stdout?.on('data', (d) => { const s = d.toString(); output += s; onData?.(s); });
|
|
147
147
|
child.stderr?.on('data', (d) => { stderr += d.toString(); });
|
|
148
148
|
child.on('close', (code) => { resolve({ output: output + stderr, exitCode: code || 0 }); });
|
|
149
149
|
child.on('error', reject);
|
|
@@ -423,7 +423,10 @@ INSTRUCCIONES:
|
|
|
423
423
|
const tryWithAutoRepair = async (cliName, model, currentCmd) => {
|
|
424
424
|
const sp = this._startSpinner(`${cliName} ${model}`);
|
|
425
425
|
try {
|
|
426
|
-
const result = await runCli(currentCmd, rolePrompt)
|
|
426
|
+
const result = await runCli(currentCmd, rolePrompt, 600000, undefined, (chunk) => {
|
|
427
|
+
this._parseChunk(chunk).forEach(l => { if (l.trim())
|
|
428
|
+
sp.push(l); });
|
|
429
|
+
});
|
|
427
430
|
if (result.exitCode !== 0) {
|
|
428
431
|
sp.stop();
|
|
429
432
|
const detail = result.output.trim().slice(0, 500);
|
|
@@ -487,7 +490,10 @@ INSTRUCCIONES:
|
|
|
487
490
|
// Retry with new command
|
|
488
491
|
const sp2 = this._startSpinner(`${cliName} ${model} (retry)`);
|
|
489
492
|
try {
|
|
490
|
-
const result = await runCli(newCmd, rolePrompt)
|
|
493
|
+
const result = await runCli(newCmd, rolePrompt, 600000, undefined, (chunk) => {
|
|
494
|
+
this._parseChunk(chunk).forEach(l => { if (l.trim())
|
|
495
|
+
sp2.push(l); });
|
|
496
|
+
});
|
|
491
497
|
sp2.stop();
|
|
492
498
|
if (result.exitCode !== 0) {
|
|
493
499
|
const detail = result.output.trim().slice(0, 500);
|
|
@@ -585,10 +591,10 @@ INSTRUCCIONES:
|
|
|
585
591
|
const preambles = {
|
|
586
592
|
orchestrator: `ROL: ORCHESTRATOR — solo planifica, nunca implementa.
|
|
587
593
|
REGLA: Tu unica salida debe ser el JSON del plan. Sin explicaciones, sin texto extra.`,
|
|
588
|
-
implementor: `ROL: IMPLEMENTOR —
|
|
589
|
-
REGLA:
|
|
590
|
-
REGLA: NO tomes decisiones de arquitectura.
|
|
591
|
-
REGLA:
|
|
594
|
+
implementor: `ROL: IMPLEMENTOR — genera el contenido completo de los archivos indicados.
|
|
595
|
+
REGLA: Para cada archivo, usa el bloque === FILE: ruta === ... === END FILE === exactamente.
|
|
596
|
+
REGLA: NO tomes decisiones de arquitectura. Sigue los steps del plan.
|
|
597
|
+
REGLA: Genera el contenido COMPLETO de cada archivo, no fragmentos ni pseudocodigo.`,
|
|
592
598
|
reviewer: `ROL: REVIEWER — solo valida, nunca modifica codigo.
|
|
593
599
|
REGLA: Usa tus herramientas (read_file, grep_search, etc.) para leer los archivos y verificar criterios.
|
|
594
600
|
REGLA: NO modifiques archivos. NO implementes correcciones.
|
|
@@ -739,41 +745,73 @@ INSTRUCCIONES:
|
|
|
739
745
|
if (await fileExists(structurePath)) {
|
|
740
746
|
structureRules = await readFile(structurePath);
|
|
741
747
|
}
|
|
742
|
-
//
|
|
748
|
+
// Read architecture context for reference
|
|
749
|
+
const context = await this.buildOrchestratorContext();
|
|
750
|
+
let archContext = '';
|
|
751
|
+
const archPath = path.join(this.projectDir, '.agent', 'context', 'architecture.md');
|
|
752
|
+
if (await fileExists(archPath)) {
|
|
753
|
+
archContext = (await readFile(archPath)).slice(0, 3000);
|
|
754
|
+
}
|
|
755
|
+
const allTargetFiles = [...new Set(plan.steps.flatMap((s) => s.files || []))];
|
|
743
756
|
const stepsText = plan.steps.map((s) => `Step ${s.num}: ${s.description}${s.files?.length ? `\n Archivos: ${s.files.join(', ')}` : ''}`).join('\n');
|
|
757
|
+
// The engine writes files itself — ask the LLM to generate content in parseable blocks.
|
|
758
|
+
// This avoids depending on CLI tool-use capabilities which are unavailable in API mode.
|
|
744
759
|
const prompt = `TAREA: ${plan.description}
|
|
745
760
|
DIRECTORIO_TRABAJO: ${this.projectDir}
|
|
746
761
|
|
|
747
|
-
STEPS A
|
|
762
|
+
STEPS A IMPLEMENTAR EN ORDEN:
|
|
748
763
|
${stepsText}
|
|
749
764
|
|
|
765
|
+
${allTargetFiles.length ? `ARCHIVOS A CREAR/MODIFICAR:\n${allTargetFiles.map(f => `- ${f}`).join('\n')}` : ''}
|
|
766
|
+
|
|
767
|
+
${context ? `CONTEXTO DEL PROYECTO:\n${context.slice(0, 2000)}\n` : ''}
|
|
768
|
+
${archContext ? `ARQUITECTURA EXISTENTE:\n${archContext}\n` : ''}
|
|
750
769
|
${structureRules ? `REGLAS DE ESTRUCTURA:\n${structureRules}\n` : ''}
|
|
751
|
-
INSTRUCCIONES:
|
|
752
|
-
1. Ejecuta cada step EN ORDEN usando tus herramientas (read_file, write_file, run_shell_command, etc.)
|
|
753
|
-
2. Para cada step: lee lo necesario, luego crea/modifica los archivos indicados.
|
|
754
|
-
3. NO tomes decisiones de arquitectura fuera del plan.
|
|
755
|
-
4. Al terminar todos los steps, responde SOLO con este JSON:
|
|
756
770
|
|
|
757
|
-
|
|
771
|
+
INSTRUCCIONES CRITICAS:
|
|
772
|
+
1. Para CADA archivo que debes crear/modificar, usa este formato EXACTO:
|
|
773
|
+
|
|
774
|
+
=== FILE: ruta/relativa/del/archivo ===
|
|
775
|
+
contenido completo del archivo aqui
|
|
776
|
+
=== END FILE ===
|
|
777
|
+
|
|
778
|
+
2. Las rutas son RELATIVAS al DIRECTORIO_TRABAJO.
|
|
779
|
+
3. Genera TODOS los archivos necesarios — uno por bloque FILE.
|
|
780
|
+
4. Incluye el contenido completo de cada archivo (no fragmentos).
|
|
781
|
+
5. Si un step modifica un archivo existente, incluye el archivo completo con los cambios.
|
|
782
|
+
6. NO incluyas explicaciones fuera de los bloques FILE.`;
|
|
758
783
|
const res = await this.runWithFallback('implementor', prompt, 'Implementacion');
|
|
759
|
-
// Extract text from streaming CLI output (qwen/claude return event arrays)
|
|
760
784
|
const text = extractCliText(res);
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
785
|
+
// Parse === FILE: path === ... === END FILE === blocks written by the LLM
|
|
786
|
+
const fileBlocks = [];
|
|
787
|
+
const fileBlockRegex = /^=== FILE: (.+?) ===\n([\s\S]*?)\n=== END FILE ===/gm;
|
|
788
|
+
let match;
|
|
789
|
+
while ((match = fileBlockRegex.exec(text)) !== null) {
|
|
790
|
+
fileBlocks.push({ relPath: match[1].trim(), content: match[2] });
|
|
765
791
|
}
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
792
|
+
const now = new Date().toISOString();
|
|
793
|
+
if (fileBlocks.length > 0) {
|
|
794
|
+
for (const { relPath, content } of fileBlocks) {
|
|
795
|
+
const absPath = path.join(this.projectDir, relPath);
|
|
796
|
+
await fs.mkdir(path.dirname(absPath), { recursive: true });
|
|
797
|
+
await writeFile(absPath, content);
|
|
798
|
+
log.ok(` Creado: ${relPath}`);
|
|
799
|
+
}
|
|
800
|
+
log.ok(`${fileBlocks.length} archivo(s) implementado(s)`);
|
|
801
|
+
}
|
|
802
|
+
else {
|
|
803
|
+
log.warn('Implementor did not return FILE blocks — no files written');
|
|
776
804
|
}
|
|
805
|
+
const progress = {
|
|
806
|
+
task_id: taskId,
|
|
807
|
+
created_at: now,
|
|
808
|
+
status: fileBlocks.length > 0 ? 'completed' : 'failed',
|
|
809
|
+
steps: plan.steps.map((s) => ({
|
|
810
|
+
...s,
|
|
811
|
+
status: fileBlocks.length > 0 ? 'completed' : 'failed',
|
|
812
|
+
completed_at: now,
|
|
813
|
+
})),
|
|
814
|
+
};
|
|
777
815
|
await writeJson(path.join(taskDir, 'progress.json'), progress);
|
|
778
816
|
log.ok('progress.json updated');
|
|
779
817
|
return progress;
|
|
@@ -11,16 +11,12 @@ export declare function qwenAuthStatus(): Promise<{
|
|
|
11
11
|
export declare function fetchQwenModels(): Promise<string[]>;
|
|
12
12
|
export declare function getQwenAccessToken(): Promise<string | null>;
|
|
13
13
|
/**
|
|
14
|
-
* Call Qwen
|
|
15
|
-
*
|
|
16
|
-
* Falls back to direct HTTP call if the qwen CLI is not available.
|
|
14
|
+
* Call Qwen REST API directly using OAuth credentials stored locally.
|
|
15
|
+
* No dependency on any qwen CLI binary.
|
|
17
16
|
*/
|
|
18
17
|
export declare function callQwenAPI(prompt: string, model?: string, onData?: (chunk: string) => void): Promise<string>;
|
|
19
18
|
/**
|
|
20
|
-
* Call Qwen API using credentials
|
|
21
|
-
*
|
|
22
|
-
* shared ~/.qwen/oauth_creds.json — we spawn it with piped stdin so it runs
|
|
23
|
-
* in non-interactive mode without TTY issues.
|
|
24
|
-
* Falls back to direct HTTP if the role binary is not found.
|
|
19
|
+
* Call Qwen API using credentials stored at a specific path (for role binaries).
|
|
20
|
+
* Calls the Qwen REST API directly — no dependency on any qwen CLI binary.
|
|
25
21
|
*/
|
|
26
22
|
export declare function callQwenAPIFromCreds(prompt: string, model: string, credsPath: string, onData?: (chunk: string) => void): Promise<string>;
|
package/dist/utils/qwen-auth.js
CHANGED
|
@@ -1,25 +1,6 @@
|
|
|
1
1
|
import * as fs from 'fs/promises';
|
|
2
2
|
import * as path from 'path';
|
|
3
3
|
import * as crypto from 'crypto';
|
|
4
|
-
import { spawn } from 'child_process';
|
|
5
|
-
/** Async alternative to spawnSync — keeps the event loop free so UI can update. */
|
|
6
|
-
function spawnAsync(bin, args, input, timeout, onData) {
|
|
7
|
-
return new Promise((resolve, reject) => {
|
|
8
|
-
const child = spawn(bin, args, { stdio: ['pipe', 'pipe', 'pipe'] });
|
|
9
|
-
let stdout = '';
|
|
10
|
-
child.stdout?.on('data', (d) => {
|
|
11
|
-
const s = d.toString();
|
|
12
|
-
stdout += s;
|
|
13
|
-
onData?.(s);
|
|
14
|
-
});
|
|
15
|
-
child.stderr?.on('data', (d) => { stdout += d.toString(); });
|
|
16
|
-
const timer = setTimeout(() => { child.kill(); reject(new Error('qwen timeout')); }, timeout);
|
|
17
|
-
child.on('close', (code) => { clearTimeout(timer); resolve({ stdout, status: code ?? 0 }); });
|
|
18
|
-
child.on('error', (err) => { clearTimeout(timer); reject(err); });
|
|
19
|
-
child.stdin?.write(input, 'utf-8');
|
|
20
|
-
child.stdin?.end();
|
|
21
|
-
});
|
|
22
|
-
}
|
|
23
4
|
import open from 'open';
|
|
24
5
|
import { AGENT_HOME } from './config.js';
|
|
25
6
|
const QWEN_OAUTH_BASE_URL = 'https://chat.qwen.ai';
|
|
@@ -267,8 +248,9 @@ export async function getQwenAccessToken() {
|
|
|
267
248
|
const token = await loadToken();
|
|
268
249
|
return token?.accessToken || null;
|
|
269
250
|
}
|
|
270
|
-
async function callQwenAPIWithToken(token, prompt, model) {
|
|
251
|
+
async function callQwenAPIWithToken(token, prompt, model, onData) {
|
|
271
252
|
const baseUrl = 'https://chat.qwen.ai/api/v1';
|
|
253
|
+
const useStream = !!onData;
|
|
272
254
|
const response = await fetch(`${baseUrl}/chat/completions`, {
|
|
273
255
|
method: 'POST',
|
|
274
256
|
headers: {
|
|
@@ -277,46 +259,65 @@ async function callQwenAPIWithToken(token, prompt, model) {
|
|
|
277
259
|
},
|
|
278
260
|
body: JSON.stringify({
|
|
279
261
|
model: model || 'coder-model',
|
|
280
|
-
messages: [
|
|
281
|
-
|
|
282
|
-
],
|
|
262
|
+
messages: [{ role: 'user', content: prompt }],
|
|
263
|
+
stream: useStream,
|
|
283
264
|
}),
|
|
284
265
|
});
|
|
285
266
|
if (!response.ok) {
|
|
286
267
|
const errorText = await response.text();
|
|
287
|
-
if (response.status === 401)
|
|
268
|
+
if (response.status === 401)
|
|
288
269
|
throw new Error(`QWEN_AUTH_EXPIRED: ${errorText}`);
|
|
289
|
-
}
|
|
290
270
|
throw new Error(`Qwen API error: ${response.status} - ${errorText}`);
|
|
291
271
|
}
|
|
292
|
-
|
|
293
|
-
|
|
272
|
+
if (!useStream) {
|
|
273
|
+
const data = await response.json();
|
|
274
|
+
return data.choices?.[0]?.message?.content || '';
|
|
275
|
+
}
|
|
276
|
+
// Streaming — parse SSE chunks
|
|
277
|
+
const reader = response.body?.getReader();
|
|
278
|
+
if (!reader)
|
|
279
|
+
throw new Error('No response body for streaming');
|
|
280
|
+
const decoder = new TextDecoder();
|
|
281
|
+
let fullText = '';
|
|
282
|
+
let buffer = '';
|
|
283
|
+
while (true) {
|
|
284
|
+
const { done, value } = await reader.read();
|
|
285
|
+
if (done)
|
|
286
|
+
break;
|
|
287
|
+
buffer += decoder.decode(value, { stream: true });
|
|
288
|
+
const lines = buffer.split('\n');
|
|
289
|
+
buffer = lines.pop() || '';
|
|
290
|
+
for (const line of lines) {
|
|
291
|
+
const trimmed = line.trim();
|
|
292
|
+
if (!trimmed.startsWith('data: '))
|
|
293
|
+
continue;
|
|
294
|
+
const data = trimmed.slice(6);
|
|
295
|
+
if (data === '[DONE]')
|
|
296
|
+
continue;
|
|
297
|
+
try {
|
|
298
|
+
const parsed = JSON.parse(data);
|
|
299
|
+
const delta = parsed.choices?.[0]?.delta?.content || '';
|
|
300
|
+
if (delta) {
|
|
301
|
+
fullText += delta;
|
|
302
|
+
onData(delta);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
catch { /* skip malformed chunks */ }
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
return fullText;
|
|
294
309
|
}
|
|
295
310
|
/**
|
|
296
|
-
* Call Qwen
|
|
297
|
-
*
|
|
298
|
-
* Falls back to direct HTTP call if the qwen CLI is not available.
|
|
311
|
+
* Call Qwen REST API directly using OAuth credentials stored locally.
|
|
312
|
+
* No dependency on any qwen CLI binary.
|
|
299
313
|
*/
|
|
300
314
|
export async function callQwenAPI(prompt, model = 'coder-model', onData) {
|
|
301
|
-
// Try using the qwen CLI subprocess first — it handles auth/refresh/format automatically
|
|
302
|
-
const qwenBin = process.env.QWEN_BIN || 'qwen';
|
|
303
|
-
try {
|
|
304
|
-
const result = await spawnAsync(qwenBin, [], prompt, 300000, onData);
|
|
305
|
-
if (result.status === 0 && result.stdout.trim()) {
|
|
306
|
-
return result.stdout.trim();
|
|
307
|
-
}
|
|
308
|
-
// qwen not available or failed — fall through to direct API
|
|
309
|
-
}
|
|
310
|
-
catch {
|
|
311
|
-
// qwen not installed — fall through
|
|
312
|
-
}
|
|
313
|
-
// Fallback: direct API call (requires valid token in AGENT_HOME)
|
|
314
315
|
let token = await loadToken();
|
|
315
316
|
if (!token) {
|
|
316
317
|
throw new Error('QWEN_AUTH_EXPIRED: No hay token de Qwen. Ejecutá --login primero.');
|
|
317
318
|
}
|
|
318
319
|
try {
|
|
319
|
-
return await callQwenAPIWithToken(token, prompt, model);
|
|
320
|
+
return await callQwenAPIWithToken(token, prompt, model, onData);
|
|
320
321
|
}
|
|
321
322
|
catch (err) {
|
|
322
323
|
if (!err.message?.startsWith('QWEN_AUTH_EXPIRED'))
|
|
@@ -325,34 +326,18 @@ export async function callQwenAPI(prompt, model = 'coder-model', onData) {
|
|
|
325
326
|
const refreshed = await doRefreshToken(token.refreshToken);
|
|
326
327
|
if (refreshed) {
|
|
327
328
|
await saveToken(refreshed);
|
|
328
|
-
return callQwenAPIWithToken(refreshed, prompt, model);
|
|
329
|
+
return callQwenAPIWithToken(refreshed, prompt, model, onData);
|
|
329
330
|
}
|
|
330
331
|
}
|
|
331
332
|
throw new Error('QWEN_AUTH_EXPIRED: Sesión expirada. Ejecutá: agent-mp --login');
|
|
332
333
|
}
|
|
333
334
|
}
|
|
334
335
|
/**
|
|
335
|
-
* Call Qwen API using credentials
|
|
336
|
-
*
|
|
337
|
-
* shared ~/.qwen/oauth_creds.json — we spawn it with piped stdin so it runs
|
|
338
|
-
* in non-interactive mode without TTY issues.
|
|
339
|
-
* Falls back to direct HTTP if the role binary is not found.
|
|
336
|
+
* Call Qwen API using credentials stored at a specific path (for role binaries).
|
|
337
|
+
* Calls the Qwen REST API directly — no dependency on any qwen CLI binary.
|
|
340
338
|
*/
|
|
341
339
|
export async function callQwenAPIFromCreds(prompt, model, credsPath, onData) {
|
|
342
|
-
// Derive the role binary name from the creds path (e.g. ~/.agent-explorer/ → agent-explorer)
|
|
343
340
|
const cliName = path.basename(path.dirname(credsPath)).replace(/^\./, '');
|
|
344
|
-
// Try spawning the qwen CLI with piped stdin (async — keeps event loop free)
|
|
345
|
-
const qwenBin = process.env.QWEN_BIN || 'qwen';
|
|
346
|
-
try {
|
|
347
|
-
const result = await spawnAsync(qwenBin, [], prompt, 300000, onData);
|
|
348
|
-
if (result.status === 0 && result.stdout.trim()) {
|
|
349
|
-
return result.stdout.trim();
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
|
-
catch {
|
|
353
|
-
// qwen not available
|
|
354
|
-
}
|
|
355
|
-
// Fallback: direct HTTP with stored creds
|
|
356
341
|
let raw;
|
|
357
342
|
try {
|
|
358
343
|
raw = JSON.parse(await fs.readFile(credsPath, 'utf-8'));
|
|
@@ -370,7 +355,6 @@ export async function callQwenAPIFromCreds(prompt, model, credsPath, onData) {
|
|
|
370
355
|
if (!token.accessToken) {
|
|
371
356
|
throw new Error(`Invalid credentials at ${credsPath}. Run: ${cliName} --login`);
|
|
372
357
|
}
|
|
373
|
-
// Refresh proactivo: si vence en menos de 2 minutos (o ya venció)
|
|
374
358
|
const TWO_MIN = 2 * 60 * 1000;
|
|
375
359
|
if (token.expiresAt - Date.now() < TWO_MIN && token.refreshToken) {
|
|
376
360
|
const refreshed = await doRefreshToken(token.refreshToken);
|
|
@@ -380,17 +364,16 @@ export async function callQwenAPIFromCreds(prompt, model, credsPath, onData) {
|
|
|
380
364
|
}
|
|
381
365
|
}
|
|
382
366
|
try {
|
|
383
|
-
return await callQwenAPIWithToken(token, prompt, model);
|
|
367
|
+
return await callQwenAPIWithToken(token, prompt, model, onData);
|
|
384
368
|
}
|
|
385
369
|
catch (err) {
|
|
386
370
|
if (!err.message?.startsWith('QWEN_AUTH_EXPIRED'))
|
|
387
371
|
throw err;
|
|
388
|
-
// 401 server-side — intentar refresh aunque expiresAt no haya vencido
|
|
389
372
|
if (token.refreshToken) {
|
|
390
373
|
const refreshed = await doRefreshToken(token.refreshToken);
|
|
391
374
|
if (refreshed) {
|
|
392
375
|
await fs.writeFile(credsPath, JSON.stringify(refreshed, null, 2), 'utf-8');
|
|
393
|
-
return callQwenAPIWithToken(refreshed, prompt, model);
|
|
376
|
+
return callQwenAPIWithToken(refreshed, prompt, model, onData);
|
|
394
377
|
}
|
|
395
378
|
}
|
|
396
379
|
throw new Error(`QWEN_AUTH_EXPIRED: Sesión expirada. Ejecutá: ${cliName} --login`);
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent-mp",
|
|
3
|
-
"version": "0.4.
|
|
4
|
-
"description": "Deterministic multi-agent CLI orchestrator
|
|
3
|
+
"version": "0.4.7",
|
|
4
|
+
"description": "Deterministic multi-agent CLI orchestrator — plan, code, review",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
7
7
|
"files": [
|
|
@@ -41,4 +41,4 @@
|
|
|
41
41
|
"engines": {
|
|
42
42
|
"node": ">=18.0.0"
|
|
43
43
|
}
|
|
44
|
-
}
|
|
44
|
+
}
|