agent-rev 0.3.1 → 0.3.3
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 +3 -12
- package/dist/core/engine.js +86 -65
- package/dist/utils/qwen-auth.js +23 -3
- package/package.json +1 -1
package/dist/commands/repl.js
CHANGED
|
@@ -7,7 +7,7 @@ import chalk from 'chalk';
|
|
|
7
7
|
import { execSync } from 'child_process';
|
|
8
8
|
import { CLI_REGISTRY } from '../types.js';
|
|
9
9
|
import { writeJson, ensureDir, readJson, listDir, fileExists } from '../utils/fs.js';
|
|
10
|
-
import { loadAuth, saveAuth, loadCliConfig, saveCliConfig, loadProjectConfig
|
|
10
|
+
import { loadAuth, saveAuth, loadCliConfig, saveCliConfig, loadProjectConfig } from '../utils/config.js';
|
|
11
11
|
import { log } from '../utils/logger.js';
|
|
12
12
|
import { AgentEngine, ExitError } from '../core/engine.js';
|
|
13
13
|
import { qwenAuthStatus, QWEN_AGENT_HOME, fetchQwenModels } from '../utils/qwen-auth.js';
|
|
@@ -1227,17 +1227,8 @@ export async function runRole(role, arg, model) {
|
|
|
1227
1227
|
}
|
|
1228
1228
|
case 'explorer':
|
|
1229
1229
|
case 'exp': {
|
|
1230
|
-
const
|
|
1231
|
-
|
|
1232
|
-
// This binary IS the configured explorer CLI — avoid recursion.
|
|
1233
|
-
// Call Qwen API directly using own credentials.
|
|
1234
|
-
const result = await engine.runExplorerDirect(arg || undefined);
|
|
1235
|
-
console.log(result);
|
|
1236
|
-
}
|
|
1237
|
-
else {
|
|
1238
|
-
const result = await engine.runExplorer(arg || undefined);
|
|
1239
|
-
console.log(result);
|
|
1240
|
-
}
|
|
1230
|
+
const result = await engine.runExplorer(arg || undefined);
|
|
1231
|
+
console.log(result);
|
|
1241
1232
|
break;
|
|
1242
1233
|
}
|
|
1243
1234
|
case 'coordinator':
|
package/dist/core/engine.js
CHANGED
|
@@ -6,7 +6,7 @@ import { CLI_REGISTRY } from '../types.js';
|
|
|
6
6
|
import { writeJson, readJson, fileExists, writeFile, readFile } from '../utils/fs.js';
|
|
7
7
|
import { log } from '../utils/logger.js';
|
|
8
8
|
import chalk from 'chalk';
|
|
9
|
-
import {
|
|
9
|
+
import { callQwenAPI, callQwenAPIFromCreds } from '../utils/qwen-auth.js';
|
|
10
10
|
import * as fs from 'fs/promises';
|
|
11
11
|
/** Thrown when a slash command inside a conversation requests exit */
|
|
12
12
|
export class ExitError extends Error {
|
|
@@ -251,55 +251,50 @@ INSTRUCCIONES:
|
|
|
251
251
|
const envOverride = {};
|
|
252
252
|
let res;
|
|
253
253
|
if (this.coordinatorCmd.startsWith('qwen')) {
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
let corporateCredsContent = null;
|
|
254
|
+
// Use Qwen API directly — avoids the qwen CLI's own OAuth flow
|
|
255
|
+
// which causes mid-session auth popups and breaks display.
|
|
257
256
|
try {
|
|
258
|
-
|
|
257
|
+
const model = this.coordinatorCmd.match(/(?:-m|--model)\s+(\S+)/)?.[1] || 'coder-model';
|
|
258
|
+
return await callQwenAPI(prompt, model);
|
|
259
259
|
}
|
|
260
|
-
catch {
|
|
261
|
-
|
|
262
|
-
|
|
260
|
+
catch (err) {
|
|
261
|
+
if (err.message?.startsWith('QWEN_AUTH_EXPIRED')) {
|
|
262
|
+
console.log(chalk.red('\n ✗ Sesión Qwen expirada.'));
|
|
263
|
+
console.log(chalk.yellow(' Ejecutá: /login para re-autenticarte.\n'));
|
|
264
|
+
}
|
|
265
|
+
else {
|
|
266
|
+
console.log(chalk.red(`\n ✗ Error Qwen: ${err.message}`));
|
|
267
|
+
}
|
|
263
268
|
return '';
|
|
264
269
|
}
|
|
265
|
-
let personalBackup = null;
|
|
266
|
-
try {
|
|
267
|
-
personalBackup = await fs.readFile(personalCreds, 'utf-8');
|
|
268
|
-
}
|
|
269
|
-
catch { }
|
|
270
|
-
await fs.writeFile(personalCreds, corporateCredsContent);
|
|
271
|
-
res = await runCli(this.coordinatorCmd, prompt, 600000, envOverride);
|
|
272
|
-
if (personalBackup) {
|
|
273
|
-
await fs.writeFile(personalCreds, personalBackup);
|
|
274
|
-
}
|
|
275
|
-
else {
|
|
276
|
-
await fs.unlink(personalCreds).catch(() => { });
|
|
277
|
-
}
|
|
278
270
|
}
|
|
279
271
|
else {
|
|
280
272
|
res = await runCli(this.coordinatorCmd, prompt, 600000, envOverride);
|
|
281
273
|
}
|
|
282
|
-
// Extract readable text
|
|
274
|
+
// Extract readable text — search for JSON array even if there's prefix text
|
|
283
275
|
let responseText = res.output.trim();
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
if (item.type === 'assistant' && item.message?.content?.length > 0) {
|
|
293
|
-
const t = item.message.content.find((c) => c.type === 'text');
|
|
294
|
-
if (t?.text) {
|
|
295
|
-
responseText = t.text;
|
|
276
|
+
const arrayStart = res.output.indexOf('[{');
|
|
277
|
+
if (arrayStart !== -1) {
|
|
278
|
+
try {
|
|
279
|
+
const json = JSON.parse(res.output.slice(arrayStart));
|
|
280
|
+
if (Array.isArray(json)) {
|
|
281
|
+
for (const item of json) {
|
|
282
|
+
if (item.type === 'result' && typeof item.result === 'string') {
|
|
283
|
+
responseText = item.result;
|
|
296
284
|
break;
|
|
297
285
|
}
|
|
286
|
+
if (item.type === 'assistant' && item.message?.content?.length > 0) {
|
|
287
|
+
const t = item.message.content.find((c) => c.type === 'text');
|
|
288
|
+
if (t?.text) {
|
|
289
|
+
responseText = t.text;
|
|
290
|
+
break;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
298
293
|
}
|
|
299
294
|
}
|
|
300
295
|
}
|
|
296
|
+
catch { }
|
|
301
297
|
}
|
|
302
|
-
catch { }
|
|
303
298
|
return responseText;
|
|
304
299
|
};
|
|
305
300
|
// Clarification loop — coordinator is only called when there is new user input
|
|
@@ -446,14 +441,39 @@ INSTRUCCIONES:
|
|
|
446
441
|
}
|
|
447
442
|
}
|
|
448
443
|
};
|
|
449
|
-
// Role binaries (agent-orch, agent-impl, etc.)
|
|
450
|
-
//
|
|
451
|
-
//
|
|
444
|
+
// Role binaries (agent-orch, agent-impl, etc.) require an interactive TTY and can't
|
|
445
|
+
// be spawned as subprocesses. Instead, if they have their own Qwen credentials
|
|
446
|
+
// (~/.agent-<name>/oauth_creds.json from running `agent-<name> --login`), call
|
|
447
|
+
// the Qwen API directly with those creds. Otherwise skip to fallback.
|
|
452
448
|
const ROLE_BINARIES = new Set(['agent-orch', 'agent-impl', 'agent-rev', 'agent-explorer']);
|
|
449
|
+
const tryRoleBinaryCreds = async (cliName, model) => {
|
|
450
|
+
const credsPath = path.join(os.homedir(), `.${cliName}`, 'oauth_creds.json');
|
|
451
|
+
if (!(await fileExists(credsPath))) {
|
|
452
|
+
log.warn(`${cliName} has no credentials — run: ${cliName} --login`);
|
|
453
|
+
return null;
|
|
454
|
+
}
|
|
455
|
+
try {
|
|
456
|
+
log.info(`${cliName}: calling Qwen API with own credentials (${model})`);
|
|
457
|
+
return await callQwenAPIFromCreds(rolePrompt, model, credsPath);
|
|
458
|
+
}
|
|
459
|
+
catch (err) {
|
|
460
|
+
if (err.message?.startsWith('QWEN_AUTH_EXPIRED')) {
|
|
461
|
+
console.log(chalk.red(`\n ✗ Sesión expirada para ${cliName}.`));
|
|
462
|
+
console.log(chalk.yellow(` Ejecutá: ${cliName} --login\n`));
|
|
463
|
+
return null;
|
|
464
|
+
}
|
|
465
|
+
log.warn(`${cliName} direct API call failed: ${err.message}`);
|
|
466
|
+
return null;
|
|
467
|
+
}
|
|
468
|
+
};
|
|
453
469
|
// Try primary
|
|
454
470
|
log.info(`Launching ${roleName}: ${role.cli} (${role.model})`);
|
|
455
471
|
if (ROLE_BINARIES.has(role.cli)) {
|
|
456
|
-
|
|
472
|
+
const directResult = await tryRoleBinaryCreds(role.cli, role.model);
|
|
473
|
+
if (directResult !== null) {
|
|
474
|
+
trackTokens(directResult, role.cli, role.model);
|
|
475
|
+
return directResult;
|
|
476
|
+
}
|
|
457
477
|
}
|
|
458
478
|
else {
|
|
459
479
|
const primaryResult = await tryWithAutoRepair(role.cli, role.model, role.cmd);
|
|
@@ -463,12 +483,21 @@ INSTRUCCIONES:
|
|
|
463
483
|
}
|
|
464
484
|
}
|
|
465
485
|
// Try individual fallback
|
|
466
|
-
if (role.fallback
|
|
486
|
+
if (role.fallback) {
|
|
467
487
|
log.warn(`Trying individual fallback for ${roleName}: ${role.fallback.cli} (${role.fallback.model})`);
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
488
|
+
if (ROLE_BINARIES.has(role.fallback.cli)) {
|
|
489
|
+
const directResult = await tryRoleBinaryCreds(role.fallback.cli, role.fallback.model);
|
|
490
|
+
if (directResult !== null) {
|
|
491
|
+
trackTokens(directResult, role.fallback.cli, role.fallback.model);
|
|
492
|
+
return directResult;
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
else {
|
|
496
|
+
const fallbackResult = await tryWithAutoRepair(role.fallback.cli, role.fallback.model, role.fallback.cmd);
|
|
497
|
+
if (fallbackResult !== null) {
|
|
498
|
+
trackTokens(fallbackResult, role.fallback.cli, role.fallback.model);
|
|
499
|
+
return fallbackResult;
|
|
500
|
+
}
|
|
472
501
|
}
|
|
473
502
|
}
|
|
474
503
|
// Try global fallback
|
|
@@ -783,26 +812,7 @@ REGLAS:
|
|
|
783
812
|
- NO modifiques archivos de aplicacion (solo .agent/context/)
|
|
784
813
|
- NO ejecutes comandos que cambien estado (npm install, migraciones, etc.)
|
|
785
814
|
- Si un directorio esta en node_modules, dist, .git: ignoralo`;
|
|
786
|
-
const
|
|
787
|
-
let res;
|
|
788
|
-
if (ROLE_BINS.has(this.config.roles.explorer.cli)) {
|
|
789
|
-
// Spawn the role binary as a separate process with its own credentials.
|
|
790
|
-
// Pass only the short task — the binary builds its own full prompt internally.
|
|
791
|
-
const explorerRole = this.config.roles.explorer;
|
|
792
|
-
const spawnCmd = `${explorerRole.cli} --model ${explorerRole.model}`;
|
|
793
|
-
log.info(`Spawning ${explorerRole.cli} (${explorerRole.model}) as separate process`);
|
|
794
|
-
const result = await runCli(spawnCmd, effectiveTask);
|
|
795
|
-
if (result.exitCode !== 0) {
|
|
796
|
-
log.warn(`${explorerRole.cli} exited with code ${result.exitCode} — trying fallback`);
|
|
797
|
-
res = await this.runWithFallback('explorer', prompt, 'Exploracion');
|
|
798
|
-
}
|
|
799
|
-
else {
|
|
800
|
-
res = result.output;
|
|
801
|
-
}
|
|
802
|
-
}
|
|
803
|
-
else {
|
|
804
|
-
res = await this.runWithFallback('explorer', prompt, 'Exploracion');
|
|
805
|
-
}
|
|
815
|
+
const res = await this.runWithFallback('explorer', prompt, 'Exploracion');
|
|
806
816
|
const text = extractCliText(res);
|
|
807
817
|
// Always save a timestamped explorer report
|
|
808
818
|
try {
|
|
@@ -843,7 +853,18 @@ INSTRUCCIONES:
|
|
|
843
853
|
3. Identifica dependencias entre servicios.
|
|
844
854
|
4. Crea/actualiza ${archPath} con tabla resumen y detalle por servicio.
|
|
845
855
|
5. Crea/actualiza ${contextDir}/<servicio>/architecture.md para cada servicio.`);
|
|
846
|
-
|
|
856
|
+
let result;
|
|
857
|
+
try {
|
|
858
|
+
result = await callQwenAPI(prompt, role.model);
|
|
859
|
+
}
|
|
860
|
+
catch (err) {
|
|
861
|
+
if (err.message?.startsWith('QWEN_AUTH_EXPIRED')) {
|
|
862
|
+
console.log(chalk.red('\n ✗ Sesión Qwen expirada.'));
|
|
863
|
+
console.log(chalk.yellow(' Ejecutá: agent-mp --login (o agent-explorer --login)\n'));
|
|
864
|
+
return '';
|
|
865
|
+
}
|
|
866
|
+
throw err;
|
|
867
|
+
}
|
|
847
868
|
try {
|
|
848
869
|
const ts = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19);
|
|
849
870
|
await writeFile(path.join(contextDir, `explorer-${ts}.md`), `# Explorer Report\n\nTask: ${effectiveTask}\n\n${result}\n`);
|
package/dist/utils/qwen-auth.js
CHANGED
|
@@ -254,6 +254,9 @@ async function callQwenAPIWithToken(token, prompt, model) {
|
|
|
254
254
|
});
|
|
255
255
|
if (!response.ok) {
|
|
256
256
|
const errorText = await response.text();
|
|
257
|
+
if (response.status === 401) {
|
|
258
|
+
throw new Error(`QWEN_AUTH_EXPIRED: ${errorText}`);
|
|
259
|
+
}
|
|
257
260
|
throw new Error(`Qwen API error: ${response.status} - ${errorText}`);
|
|
258
261
|
}
|
|
259
262
|
const data = await response.json();
|
|
@@ -262,9 +265,17 @@ async function callQwenAPIWithToken(token, prompt, model) {
|
|
|
262
265
|
export async function callQwenAPI(prompt, model = 'coder-model') {
|
|
263
266
|
const token = await loadToken();
|
|
264
267
|
if (!token) {
|
|
265
|
-
throw new Error('No hay token de Qwen. Ejecutá
|
|
268
|
+
throw new Error('QWEN_AUTH_EXPIRED: No hay token de Qwen. Ejecutá --login primero.');
|
|
269
|
+
}
|
|
270
|
+
try {
|
|
271
|
+
return await callQwenAPIWithToken(token, prompt, model);
|
|
272
|
+
}
|
|
273
|
+
catch (err) {
|
|
274
|
+
if (err.message?.startsWith('QWEN_AUTH_EXPIRED')) {
|
|
275
|
+
throw new Error('QWEN_AUTH_EXPIRED: Sesión expirada. Ejecutá: agent-mp --login');
|
|
276
|
+
}
|
|
277
|
+
throw err;
|
|
266
278
|
}
|
|
267
|
-
return callQwenAPIWithToken(token, prompt, model);
|
|
268
279
|
}
|
|
269
280
|
/** Call Qwen API using credentials from a specific file path (for role binaries) */
|
|
270
281
|
export async function callQwenAPIFromCreds(prompt, model, credsPath) {
|
|
@@ -293,5 +304,14 @@ export async function callQwenAPIFromCreds(prompt, model, credsPath) {
|
|
|
293
304
|
await fs.writeFile(credsPath, JSON.stringify(token, null, 2), 'utf-8');
|
|
294
305
|
}
|
|
295
306
|
}
|
|
296
|
-
|
|
307
|
+
try {
|
|
308
|
+
return await callQwenAPIWithToken(token, prompt, model);
|
|
309
|
+
}
|
|
310
|
+
catch (err) {
|
|
311
|
+
if (err.message?.startsWith('QWEN_AUTH_EXPIRED')) {
|
|
312
|
+
const cliName = path.basename(path.dirname(credsPath)).replace(/^\./, '');
|
|
313
|
+
throw new Error(`QWEN_AUTH_EXPIRED: Sesión expirada. Ejecutá: ${cliName} --login`);
|
|
314
|
+
}
|
|
315
|
+
throw err;
|
|
316
|
+
}
|
|
297
317
|
}
|
package/package.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"name":"agent-rev","version":"0.3.
|
|
1
|
+
{"name":"agent-rev","version":"0.3.3","description":"agent-rev agent","type":"module","main":"./dist/index.js","files":["dist/"],"bin":{"agent-rev":"dist/index.js"},"scripts":{"build":"tsc"},"keywords":["ai","agent","cli"],"license":"MIT","dependencies":{"@anthropic-ai/sdk":"^0.39.0","@google/generative-ai":"^0.24.0","chalk":"^5.4.1","commander":"^13.1.0","open":"^11.0.0","openai":"^4.91.0"},"engines":{"node":">=18.0.0"}}
|