agent-rev 0.3.0 → 0.3.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.
@@ -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 } from '../utils/config.js';
10
+ import { loadAuth, saveAuth, loadCliConfig, saveCliConfig, loadProjectConfig, PKG_NAME } 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,8 +1227,17 @@ export async function runRole(role, arg, model) {
1227
1227
  }
1228
1228
  case 'explorer':
1229
1229
  case 'exp': {
1230
- const result = await engine.runExplorer(arg || undefined);
1231
- console.log(result);
1230
+ const explorerCli = config.roles.explorer?.cli;
1231
+ if (explorerCli && explorerCli === PKG_NAME) {
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
+ }
1232
1241
  break;
1233
1242
  }
1234
1243
  case 'coordinator':
@@ -39,5 +39,8 @@ export declare class AgentEngine {
39
39
  verdict: string;
40
40
  }>;
41
41
  runExplorer(task?: string): Promise<string>;
42
+ /** Called when the current binary IS the configured explorer CLI (prevents recursion).
43
+ * Builds the full exploration prompt and calls Qwen API using own credentials. */
44
+ runExplorerDirect(task?: string): Promise<string>;
42
45
  runFullCycle(task: string): Promise<void>;
43
46
  }
@@ -6,8 +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 { QWEN_AGENT_HOME } from '../utils/qwen-auth.js';
10
- import { callQwenAPI } from '../utils/qwen-auth.js';
9
+ import { QWEN_AGENT_HOME, callQwenAPI } from '../utils/qwen-auth.js';
11
10
  import * as fs from 'fs/promises';
12
11
  /** Thrown when a slash command inside a conversation requests exit */
13
12
  export class ExitError extends Error {
@@ -448,12 +447,13 @@ INSTRUCCIONES:
448
447
  }
449
448
  };
450
449
  // Role binaries (agent-orch, agent-impl, etc.) are wrappers, not AI CLIs.
451
- // Calling them with a full prompt causes recursion or ENAMETOOLONG errors.
450
+ // runExplorer handles them separately by spawning with the short task.
451
+ // For orch/impl/rev the full cycle never spawns role binaries via runWithFallback.
452
452
  const ROLE_BINARIES = new Set(['agent-orch', 'agent-impl', 'agent-rev', 'agent-explorer']);
453
453
  // Try primary
454
454
  log.info(`Launching ${roleName}: ${role.cli} (${role.model})`);
455
455
  if (ROLE_BINARIES.has(role.cli)) {
456
- log.warn(`${role.cli} is a role binary — skipping to fallback to avoid recursion`);
456
+ log.warn(`${role.cli} is a role binary — skipping to fallback`);
457
457
  }
458
458
  else {
459
459
  const primaryResult = await tryWithAutoRepair(role.cli, role.model, role.cmd);
@@ -783,7 +783,26 @@ REGLAS:
783
783
  - NO modifiques archivos de aplicacion (solo .agent/context/)
784
784
  - NO ejecutes comandos que cambien estado (npm install, migraciones, etc.)
785
785
  - Si un directorio esta en node_modules, dist, .git: ignoralo`;
786
- const res = await this.runWithFallback('explorer', prompt, 'Exploracion');
786
+ const ROLE_BINS = new Set(['agent-orch', 'agent-impl', 'agent-rev', 'agent-explorer']);
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
+ }
787
806
  const text = extractCliText(res);
788
807
  // Always save a timestamped explorer report
789
808
  try {
@@ -794,6 +813,45 @@ REGLAS:
794
813
  catch { /* don't fail if save fails */ }
795
814
  return text;
796
815
  }
816
+ /** Called when the current binary IS the configured explorer CLI (prevents recursion).
817
+ * Builds the full exploration prompt and calls Qwen API using own credentials. */
818
+ async runExplorerDirect(task) {
819
+ const role = this.config.roles.explorer;
820
+ if (!role)
821
+ throw new Error('Explorer role not configured.');
822
+ const agentDir = path.join(this.projectDir, '.agent');
823
+ const contextDir = path.join(agentDir, 'context');
824
+ await fs.mkdir(contextDir, { recursive: true });
825
+ const archPath = path.join(contextDir, 'architecture.md');
826
+ let existingArch = '';
827
+ try {
828
+ existingArch = await readFile(archPath);
829
+ }
830
+ catch { /* new */ }
831
+ const effectiveTask = task || 'Explorar y documentar todas las aplicaciones y servicios del proyecto';
832
+ const context = await this.buildOrchestratorContext();
833
+ const prompt = this.buildRolePrompt('explorer', `TAREA DE EXPLORACION: ${effectiveTask}
834
+ DIRECTORIO_TRABAJO: ${this.projectDir}
835
+ PROYECTO: ${this.config.project}
836
+
837
+ ${existingArch ? `DOCUMENTACION EXISTENTE:\n${existingArch.slice(0, 3000)}\n` : 'Sin documentacion previa.\n'}
838
+ CONTEXTO: ${context.slice(0, 2000)}
839
+
840
+ INSTRUCCIONES:
841
+ 1. Lista el directorio raiz e identifica todos los servicios/apps.
842
+ 2. Para cada uno: lee package.json/requirements.txt, explora src/, identifica entry point, rutas/endpoints, puerto.
843
+ 3. Identifica dependencias entre servicios.
844
+ 4. Crea/actualiza ${archPath} con tabla resumen y detalle por servicio.
845
+ 5. Crea/actualiza ${contextDir}/<servicio>/architecture.md para cada servicio.`);
846
+ const result = await callQwenAPI(prompt, role.model);
847
+ try {
848
+ const ts = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19);
849
+ await writeFile(path.join(contextDir, `explorer-${ts}.md`), `# Explorer Report\n\nTask: ${effectiveTask}\n\n${result}\n`);
850
+ log.ok(`Saved to .agent/context/explorer-${ts}.md`);
851
+ }
852
+ catch { /* ignore */ }
853
+ return result;
854
+ }
797
855
  async runFullCycle(task) {
798
856
  // Header is now shown by the REPL before the first user message
799
857
  // ══════════════════════════════════════════════════
@@ -11,3 +11,5 @@ export declare function qwenAuthStatus(): Promise<{
11
11
  export declare function fetchQwenModels(): Promise<string[]>;
12
12
  export declare function getQwenAccessToken(): Promise<string | null>;
13
13
  export declare function callQwenAPI(prompt: string, model?: string): Promise<string>;
14
+ /** Call Qwen API using credentials from a specific file path (for role binaries) */
15
+ export declare function callQwenAPIFromCreds(prompt: string, model: string, credsPath: string): Promise<string>;
@@ -237,12 +237,7 @@ export async function getQwenAccessToken() {
237
237
  const token = await loadToken();
238
238
  return token?.accessToken || null;
239
239
  }
240
- export async function callQwenAPI(prompt, model = 'coder-model') {
241
- const token = await loadToken();
242
- if (!token) {
243
- throw new Error('No hay token de Qwen. Ejecutá /login primero.');
244
- }
245
- // Qwen OAuth tokens work with chat.qwen.ai API
240
+ async function callQwenAPIWithToken(token, prompt, model) {
246
241
  const baseUrl = 'https://chat.qwen.ai/api/v1';
247
242
  const response = await fetch(`${baseUrl}/chat/completions`, {
248
243
  method: 'POST',
@@ -253,7 +248,6 @@ export async function callQwenAPI(prompt, model = 'coder-model') {
253
248
  body: JSON.stringify({
254
249
  model: model || 'coder-model',
255
250
  messages: [
256
- { role: 'system', content: 'Sos el COORDINADOR de un equipo multi-agente de desarrollo. Tu trabajo es ENTENDER lo que el programador necesita haciendo PREGUNTAS si es necesario. Habla de forma NATURAL, como un compañero de equipo. Sé breve y directo.' },
257
251
  { role: 'user', content: prompt },
258
252
  ],
259
253
  }),
@@ -265,3 +259,39 @@ export async function callQwenAPI(prompt, model = 'coder-model') {
265
259
  const data = await response.json();
266
260
  return data.choices?.[0]?.message?.content || '';
267
261
  }
262
+ export async function callQwenAPI(prompt, model = 'coder-model') {
263
+ const token = await loadToken();
264
+ if (!token) {
265
+ throw new Error('No hay token de Qwen. Ejecutá /login primero.');
266
+ }
267
+ return callQwenAPIWithToken(token, prompt, model);
268
+ }
269
+ /** Call Qwen API using credentials from a specific file path (for role binaries) */
270
+ export async function callQwenAPIFromCreds(prompt, model, credsPath) {
271
+ let raw;
272
+ try {
273
+ raw = JSON.parse(await fs.readFile(credsPath, 'utf-8'));
274
+ }
275
+ catch {
276
+ throw new Error(`No credentials found at ${credsPath}. Run the role binary with --login first.`);
277
+ }
278
+ let token = {
279
+ accessToken: raw.accessToken || raw.access_token || '',
280
+ refreshToken: raw.refreshToken || raw.refresh_token || '',
281
+ expiresAt: raw.expiresAt || raw.expiry_date || 0,
282
+ idToken: raw.idToken || raw.id_token,
283
+ resourceUrl: raw.resourceUrl || raw.resource_url,
284
+ };
285
+ if (!token.accessToken) {
286
+ throw new Error(`Invalid credentials at ${credsPath}. Run the role binary with --login first.`);
287
+ }
288
+ // Refresh if expired
289
+ if (token.expiresAt <= Date.now() && token.refreshToken) {
290
+ const refreshed = await refreshAccessToken(token.refreshToken);
291
+ if (refreshed) {
292
+ token = refreshed;
293
+ await fs.writeFile(credsPath, JSON.stringify(token, null, 2), 'utf-8');
294
+ }
295
+ }
296
+ return callQwenAPIWithToken(token, prompt, model);
297
+ }
package/package.json CHANGED
@@ -1,23 +1 @@
1
- {
2
- "name": "agent-rev",
3
- "version": "0.3.0",
4
- "description": "agent-rev agent",
5
- "type": "module",
6
- "main": "./dist/index.js",
7
- "files": ["dist/"],
8
- "bin": { "agent-rev": "dist/index.js" },
9
- "scripts": {
10
- "build": "tsc && echo '#!/usr/bin/env node' | cat - dist/index.js > dist/index.tmp && mv dist/index.tmp dist/index.js && chmod +x dist/index.js"
11
- },
12
- "keywords": ["ai", "agent", "cli"],
13
- "license": "MIT",
14
- "dependencies": {
15
- "@anthropic-ai/sdk": "^0.39.0",
16
- "@google/generative-ai": "^0.24.0",
17
- "chalk": "^5.4.1",
18
- "commander": "^13.1.0",
19
- "open": "^11.0.0",
20
- "openai": "^4.91.0"
21
- },
22
- "engines": { "node": ">=18.0.0" }
23
- }
1
+ {"name":"agent-rev","version":"0.3.1","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"}}