agent-rev 0.5.11 → 0.5.12
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 +75 -0
- package/dist/core/engine.js +393 -34
- package/dist/index.js +34 -0
- package/dist/utils/config.d.ts +4 -3
- package/dist/utils/config.js +16 -33
- package/dist/utils/qwen-auth.d.ts +15 -0
- package/dist/utils/qwen-auth.js +89 -1
- package/package.json +1 -1
package/dist/commands/repl.js
CHANGED
|
@@ -683,6 +683,57 @@ async function cmdAuthStatus(fi) {
|
|
|
683
683
|
}));
|
|
684
684
|
fi.println(renderSectionBox('Auth Status', rows));
|
|
685
685
|
}
|
|
686
|
+
async function cmdUsage(fi) {
|
|
687
|
+
const { qwenUsage } = await import('../utils/qwen-auth.js');
|
|
688
|
+
const { PKG_NAME, getConfigDir } = await import('../utils/config.js');
|
|
689
|
+
const { loadProjectConfig } = await import('../utils/config.js');
|
|
690
|
+
fi.println(chalk.bold.cyan('\n Uso / Quota\n'));
|
|
691
|
+
// Check each role CLI
|
|
692
|
+
const dir = await resolveProjectDir(process.cwd());
|
|
693
|
+
let config;
|
|
694
|
+
try {
|
|
695
|
+
config = await loadProjectConfig(dir);
|
|
696
|
+
}
|
|
697
|
+
catch {
|
|
698
|
+
config = null;
|
|
699
|
+
}
|
|
700
|
+
const checks = [];
|
|
701
|
+
// Check main PKG_NAME (this CLI's own credentials)
|
|
702
|
+
const mainResult = await qwenUsage();
|
|
703
|
+
const mainLabel = `${PKG_NAME} (${mainResult.token ? 'coder-model' : '?'})`;
|
|
704
|
+
if (mainResult.ok) {
|
|
705
|
+
const exp = mainResult.token ? new Date(mainResult.token.expiresAt).toLocaleTimeString() : '?';
|
|
706
|
+
checks.push({ key: mainLabel, value: chalk.green(`✓ quota OK — expires ${exp}`) });
|
|
707
|
+
}
|
|
708
|
+
else {
|
|
709
|
+
checks.push({ key: mainLabel, value: chalk.red(`✗ ${mainResult.error}`) });
|
|
710
|
+
}
|
|
711
|
+
// Check each configured role CLI
|
|
712
|
+
if (config?.roles) {
|
|
713
|
+
for (const [roleName, roleCfg] of Object.entries(config.roles)) {
|
|
714
|
+
const r = roleCfg;
|
|
715
|
+
if (!r?.cli)
|
|
716
|
+
continue;
|
|
717
|
+
const homeDir = path.join(os.homedir(), '.' + r.cli);
|
|
718
|
+
const credsPath = path.join(homeDir, 'oauth_creds.json');
|
|
719
|
+
if (await fileExists(credsPath)) {
|
|
720
|
+
const result = await qwenUsage(credsPath);
|
|
721
|
+
const exp = result.token ? new Date(result.token.expiresAt).toLocaleTimeString() : '?';
|
|
722
|
+
if (result.ok) {
|
|
723
|
+
checks.push({ key: `${r.cli} (${roleName})`, value: chalk.green(`✓ quota OK — expires ${exp}`) });
|
|
724
|
+
}
|
|
725
|
+
else {
|
|
726
|
+
checks.push({ key: `${r.cli} (${roleName})`, value: chalk.red(`✗ ${result.error}`) });
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
else {
|
|
730
|
+
checks.push({ key: `${r.cli} (${roleName})`, value: chalk.yellow('not logged in') });
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
fi.println(renderSectionBox('Quota Status', checks));
|
|
735
|
+
fi.println('');
|
|
736
|
+
}
|
|
686
737
|
function cmdHelp(fi) {
|
|
687
738
|
const commands = [
|
|
688
739
|
{ key: '/setup', value: 'Full interactive setup wizard' },
|
|
@@ -703,6 +754,7 @@ function cmdHelp(fi) {
|
|
|
703
754
|
{ key: '/login', value: 'Login (Qwen OAuth or CLI auth)' },
|
|
704
755
|
{ key: '/logout', value: 'Logout and clear credentials' },
|
|
705
756
|
{ key: '/auth-status', value: 'Show authentication status' },
|
|
757
|
+
{ key: '/usage', value: 'Check quota status for all role CLIs' },
|
|
706
758
|
{ key: '/tasks', value: 'List all tasks' },
|
|
707
759
|
{ key: '/clear', value: 'Clear the screen' },
|
|
708
760
|
{ key: '/help', value: 'Show this help' },
|
|
@@ -994,6 +1046,16 @@ export async function runRepl(resumeSession) {
|
|
|
994
1046
|
const dir = await resolveProjectDir(process.cwd());
|
|
995
1047
|
const config = await loadProjectConfig(dir);
|
|
996
1048
|
if (args[0] === 'orch' || args[0] === 'orchestrator') {
|
|
1049
|
+
// Ensure documentation exists before orchestrator runs
|
|
1050
|
+
const contextDir = path.join(dir, '.agent', 'context');
|
|
1051
|
+
const archPath = path.join(contextDir, 'architecture.md');
|
|
1052
|
+
if (!(await fileExists(archPath))) {
|
|
1053
|
+
fi.println(chalk.yellow('\n ── Generando documentación del proyecto ──'));
|
|
1054
|
+
fi.println(chalk.dim(' El orquestador necesita conocer la estructura del proyecto primero.\n'));
|
|
1055
|
+
const prepEngine = new AgentEngine(config, dir, gCoordinatorCmd, rl, fi, handleCmd);
|
|
1056
|
+
await prepEngine.runExplorer('Document the complete project structure, services, dependencies, and entry points. Create/update .agent/context/architecture.md');
|
|
1057
|
+
fi.println(chalk.green(' ✓ Documentación del proyecto generada.\n'));
|
|
1058
|
+
}
|
|
997
1059
|
const task = args.slice(1).join(' ');
|
|
998
1060
|
const engine = new AgentEngine(config, dir, gCoordinatorCmd, rl, fi, handleCmd);
|
|
999
1061
|
const result = await engine.runOrchestrator(task);
|
|
@@ -1057,6 +1119,9 @@ export async function runRepl(resumeSession) {
|
|
|
1057
1119
|
case 'auth-status':
|
|
1058
1120
|
await cmdAuthStatus(fi);
|
|
1059
1121
|
break;
|
|
1122
|
+
case 'usage':
|
|
1123
|
+
await cmdUsage(fi);
|
|
1124
|
+
break;
|
|
1060
1125
|
case 'tasks':
|
|
1061
1126
|
await cmdTasks(fi);
|
|
1062
1127
|
break;
|
|
@@ -1106,6 +1171,16 @@ export async function runRepl(resumeSession) {
|
|
|
1106
1171
|
fi.println(chalk.red(' No roles configured. Run /config-multi first.'));
|
|
1107
1172
|
}
|
|
1108
1173
|
else {
|
|
1174
|
+
// Before running the orchestrator, ensure project documentation exists
|
|
1175
|
+
const contextDir = path.join(dir, '.agent', 'context');
|
|
1176
|
+
const archPath = path.join(contextDir, 'architecture.md');
|
|
1177
|
+
if (!(await fileExists(archPath))) {
|
|
1178
|
+
fi.println(chalk.yellow('\n ── Generando documentación del proyecto ──'));
|
|
1179
|
+
fi.println(chalk.dim(' El orquestador necesita conocer la estructura del proyecto primero.\n'));
|
|
1180
|
+
const engine = new AgentEngine(config, dir, gCoordinatorCmd, rl, fi, handleCmd);
|
|
1181
|
+
await engine.runExplorer('Document the complete project structure, services, dependencies, and entry points. Create/update .agent/context/architecture.md');
|
|
1182
|
+
fi.println(chalk.green(' ✓ Documentación del proyecto generada.\n'));
|
|
1183
|
+
}
|
|
1109
1184
|
const engine = new AgentEngine(config, dir, gCoordinatorCmd, rl, fi, handleCmd);
|
|
1110
1185
|
await engine.runFullCycle(trimmed);
|
|
1111
1186
|
session.messages.push({ role: 'agent', content: `[task cycle completed for: ${trimmed}]`, ts: new Date().toISOString() });
|
package/dist/core/engine.js
CHANGED
|
@@ -545,6 +545,10 @@ INSTRUCCIONES:
|
|
|
545
545
|
log.warn(`${cliName} session expired — using fallback`);
|
|
546
546
|
return null;
|
|
547
547
|
}
|
|
548
|
+
if (err.message?.startsWith('QWEN_QUOTA_EXCEEDED')) {
|
|
549
|
+
log.warn(`${cliName} quota exhausted — using fallback`);
|
|
550
|
+
return null;
|
|
551
|
+
}
|
|
548
552
|
log.warn(`${cliName} direct API call failed: ${err.message}`);
|
|
549
553
|
return null;
|
|
550
554
|
}
|
|
@@ -981,40 +985,364 @@ INSTRUCCIONES:
|
|
|
981
985
|
await fs.mkdir(path.join(agentDir, 'rules'), { recursive: true });
|
|
982
986
|
// Clean up stray files before running
|
|
983
987
|
await this._cleanContextDir(contextDir);
|
|
984
|
-
//
|
|
985
|
-
|
|
986
|
-
let existingArch = '';
|
|
988
|
+
// Build a quick filesystem snapshot to give the explorer context
|
|
989
|
+
let fsSnapshot = '';
|
|
987
990
|
try {
|
|
988
|
-
|
|
991
|
+
const { execSync } = await import('child_process');
|
|
992
|
+
fsSnapshot = execSync(`find "${this.projectDir}" -maxdepth 3 -not -path '*/.agent/*' -not -path '*/node_modules/*' -not -path '*/.git/*' -not -path '*/dist/*' -not -path '*/__pycache__/*' -not -path '*/.venv/*' -not -path '*/target/*' -not -path '*/build/*' -not -name '.git' | sort | head -300`, { encoding: 'utf-8', timeout: 10000 });
|
|
993
|
+
}
|
|
994
|
+
catch { /* ignore */ }
|
|
995
|
+
// Read existing main architecture doc
|
|
996
|
+
const mainArchPath = path.join(contextDir, 'architecture.md');
|
|
997
|
+
let existingMainArch = '';
|
|
998
|
+
try {
|
|
999
|
+
existingMainArch = await readFile(mainArchPath);
|
|
989
1000
|
}
|
|
990
|
-
catch { /* new
|
|
1001
|
+
catch { /* new */ }
|
|
1002
|
+
// Read existing per-component docs
|
|
1003
|
+
let existingComponentDocs = '';
|
|
1004
|
+
try {
|
|
1005
|
+
const ctxEntries = await fs.readdir(contextDir);
|
|
1006
|
+
for (const entry of ctxEntries.filter(e => e !== 'architecture.md' && e !== 'explorer-last.md' && e !== 'modules')) {
|
|
1007
|
+
const entryPath = path.join(contextDir, entry);
|
|
1008
|
+
const stat = await fs.stat(entryPath);
|
|
1009
|
+
if (stat.isDirectory()) {
|
|
1010
|
+
const compArch = path.join(entryPath, 'architecture.md');
|
|
1011
|
+
if (await fileExists(compArch)) {
|
|
1012
|
+
existingComponentDocs += `\n=== EXISTING: ${entry}/architecture.md ===\n${(await readFile(compArch)).slice(0, 1500)}\n`;
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
catch { /* ignore */ }
|
|
991
1018
|
const effectiveTask = task || 'Explorar y documentar todas las aplicaciones y servicios del proyecto';
|
|
992
1019
|
const prompt = `TAREA DE EXPLORACION: ${effectiveTask}
|
|
993
1020
|
DIRECTORIO_TRABAJO: ${this.projectDir}
|
|
994
1021
|
PROYECTO: ${this.config.project}
|
|
995
1022
|
STACK: ${this.config.stack}
|
|
996
1023
|
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1024
|
+
ESTRUCTURA DE ARCHIVOS (excluye .agent, node_modules, .git, dist, __pycache__, .venv, target, build):
|
|
1025
|
+
${fsSnapshot}
|
|
1026
|
+
|
|
1027
|
+
${existingMainArch ? `=== DOC EXISTENTE: architecture.md (principal) ===\n${existingMainArch.slice(0, 2000)}\n` : ''}
|
|
1028
|
+
${existingComponentDocs ? `=== DOC EXISTENTE por componente ===\n${existingComponentDocs.slice(0, 3000)}\n` : ''}
|
|
1029
|
+
|
|
1030
|
+
================================================================
|
|
1031
|
+
OBJETIVO
|
|
1032
|
+
================================================================
|
|
1033
|
+
Generar documentacion ESCALONADA en 3 niveles, util TANTO para personas funcionales (PM, negocio) como tecnicas (devs).
|
|
1034
|
+
Detallada pero NO excesiva. Concreta, con datos REALES leidos de los archivos. Cero especulaciones.
|
|
1035
|
+
|
|
1036
|
+
================================================================
|
|
1037
|
+
REGLA DE ORO — PROHIBIDO INVENTAR
|
|
1038
|
+
================================================================
|
|
1039
|
+
NO uses jamas: "Inferido", "Probablemente", "Posiblemente", "Asumido", "(supuesto)", "(quizas)", "parece ser".
|
|
1040
|
+
Si no podes verificar un dato leyendo un archivo concreto → OMITELO. Es mejor una doc corta y veraz que una larga llena de suposiciones.
|
|
1041
|
+
Cada puerto, version, endpoint, env var, schema o ruta que aparezca en la doc DEBE haber sido leido de un archivo real.
|
|
1042
|
+
|
|
1043
|
+
================================================================
|
|
1044
|
+
WORKFLOW QUE TENES QUE EJECUTAR (usa tus tools de read_file/grep_search/list_directory)
|
|
1045
|
+
================================================================
|
|
1046
|
+
1. Lista DIRECTORIO_TRABAJO. Identifica los componentes (apps/services/libs). Descarta lo trivial (solo configs/docs/scripts).
|
|
1047
|
+
2. Para CADA componente no trivial, LEE COMO MINIMO:
|
|
1048
|
+
a. Manifest del proyecto: package.json | requirements.txt | pyproject.toml | pom.xml | build.gradle | Cargo.toml | go.mod
|
|
1049
|
+
→ de ahi sacas: nombre, version, framework, dependencias clave con sus versiones, scripts/tasks, entry point
|
|
1050
|
+
b. Entry point real: src/main.ts | index.js | app.py | Application.java | cmd/main.go (segun stack)
|
|
1051
|
+
→ de ahi sacas: puerto, middlewares, modulos cargados, configuracion inicial
|
|
1052
|
+
c. Archivo de variables de entorno: .env | .env.example | application.yml | config.* | settings.py
|
|
1053
|
+
→ de ahi sacas: variables reales con su proposito
|
|
1054
|
+
d. Al menos 2-3 controladores/routers/handlers principales
|
|
1055
|
+
→ de ahi extraes endpoints REALES (metodo, ruta, parametros)
|
|
1056
|
+
e. Al menos 2-3 modelos/schemas/entities principales
|
|
1057
|
+
→ de ahi extraes el shape de datos
|
|
1058
|
+
f. README.md del componente si existe
|
|
1059
|
+
3. Mapea RELACIONES REALES entre componentes (no asumas):
|
|
1060
|
+
- Buscando URLs/hosts de otros servicios en .env
|
|
1061
|
+
- Buscando imports cross-component
|
|
1062
|
+
- Buscando cadenas de conexion a BBDD/colas/cache
|
|
1063
|
+
4. Recien con esa informacion concreta, genera los archivos de documentacion.
|
|
1064
|
+
|
|
1065
|
+
================================================================
|
|
1066
|
+
LENGUAJE DUAL (regla critica)
|
|
1067
|
+
================================================================
|
|
1068
|
+
Cada seccion abre con UNA linea en lenguaje simple (que entienda un PM/negocio), DESPUES viene el detalle tecnico.
|
|
1069
|
+
- MAL: "El microservicio orquesta la persistencia mediante un repositorio."
|
|
1070
|
+
- BIEN: "Guarda la informacion del usuario en la base. Internamente usa un repositorio que abstrae la conexion a MongoDB."
|
|
1071
|
+
|
|
1072
|
+
================================================================
|
|
1073
|
+
ESTRUCTURA DE LOS 3 NIVELES
|
|
1074
|
+
================================================================
|
|
1075
|
+
|
|
1076
|
+
────────────────────────────────────────────────────────────────
|
|
1077
|
+
NIVEL 0 — architecture.md (raiz de .agent/context/)
|
|
1078
|
+
Indice global. Objetivo: 50-150 lineas. Concreto.
|
|
1079
|
+
────────────────────────────────────────────────────────────────
|
|
1080
|
+
Secciones obligatorias:
|
|
1081
|
+
|
|
1082
|
+
# ${this.config.project} — Arquitectura Global (NIVEL 0)
|
|
1083
|
+
|
|
1084
|
+
> Indice de servicios y relaciones. Lectura escalonada:
|
|
1085
|
+
> NIVEL 0: este archivo
|
|
1086
|
+
> NIVEL 1: .agent/context/[componente]/architecture.md
|
|
1087
|
+
> NIVEL 2: .agent/context/[componente]/modules/[modulo].md
|
|
1088
|
+
|
|
1089
|
+
## 1. Overview funcional
|
|
1090
|
+
2-4 lineas en lenguaje simple: que es el sistema, para quien sirve, que problema resuelve.
|
|
1091
|
+
|
|
1092
|
+
## 2. Componentes del proyecto
|
|
1093
|
+
| Componente | Tipo | Stack + version | Puerto | Proposito (negocio) | Entry point |
|
|
1094
|
+
|------------|------|-----------------|--------|---------------------|-------------|
|
|
1095
|
+
(una fila por componente, con datos REALES)
|
|
1096
|
+
|
|
1097
|
+
## 3. Relaciones entre componentes
|
|
1098
|
+
| Origen | Destino | Tipo (HTTP / Import / DB / Queue / CORS) | Proposito |
|
|
1099
|
+
|--------|---------|------------------------------------------|-----------|
|
|
1100
|
+
(una fila por cada relacion confirmada)
|
|
1101
|
+
|
|
1102
|
+
## 4. Diagrama de arquitectura
|
|
1103
|
+
\`\`\`
|
|
1104
|
+
[Frontend Web] ──HTTP──> [API Auth :8081]
|
|
1105
|
+
│ │
|
|
1106
|
+
│ v
|
|
1107
|
+
└──HTTP──> [API Core :8080] ──> [MongoDB]
|
|
1108
|
+
\`\`\`
|
|
1109
|
+
|
|
1110
|
+
## 5. Flujos end-to-end principales (3-5)
|
|
1111
|
+
Mappings funcional → tecnico, paso a paso. Ejemplo:
|
|
1112
|
+
- **"Un usuario crea un pedido"**: WebApp → POST /orders (API Core) → valida JWT contra API Auth → guarda en MongoDB → devuelve 201 con el pedido.
|
|
1113
|
+
|
|
1114
|
+
## 6. Prerequisitos para levantar el proyecto
|
|
1115
|
+
Versiones REALES leidas de los manifests (no rangos genericos).
|
|
1116
|
+
|
|
1117
|
+
## 7. Comandos globales de desarrollo
|
|
1118
|
+
| Comando | Descripcion | Directorio |
|
|
1119
|
+
|
|
1120
|
+
## 8. Decisiones de arquitectura globales (opcional, solo si hay)
|
|
1121
|
+
|
|
1122
|
+
────────────────────────────────────────────────────────────────
|
|
1123
|
+
NIVEL 1 — [componente]/architecture.md
|
|
1124
|
+
Detalle del componente. Objetivo: 80-200 lineas. Crear UNO POR CADA componente NO trivial.
|
|
1125
|
+
────────────────────────────────────────────────────────────────
|
|
1126
|
+
Secciones obligatorias:
|
|
1127
|
+
|
|
1128
|
+
# [componente] — Arquitectura (NIVEL 1)
|
|
1129
|
+
|
|
1130
|
+
## Que hace
|
|
1131
|
+
2-3 lineas en lenguaje simple, sin tecnicismos.
|
|
1132
|
+
|
|
1133
|
+
## Casos de uso principales
|
|
1134
|
+
3-6 bullets en formato negocio:
|
|
1135
|
+
- "Permite al usuario X..."
|
|
1136
|
+
- "El sistema usa este servicio para Y..."
|
|
1137
|
+
|
|
1138
|
+
## Stack tecnico
|
|
1139
|
+
| Item | Valor |
|
|
1140
|
+
|------|-------|
|
|
1141
|
+
| Lenguaje | TypeScript 5.3 |
|
|
1142
|
+
| Framework | NestJS 11.0.1 |
|
|
1143
|
+
| ORM | Mongoose 9.3.3 |
|
|
1144
|
+
| Auth | Passport JWT |
|
|
1145
|
+
(datos reales del manifest)
|
|
1146
|
+
|
|
1147
|
+
## Puerto y URLs
|
|
1148
|
+
- **Puerto:** 8085 (de PORT en .env)
|
|
1149
|
+
- **Swagger / docs:** http://localhost:8085/api/docs (si aplica)
|
|
1150
|
+
- **Health check:** GET /health (si existe)
|
|
1151
|
+
|
|
1152
|
+
## Estructura interna (arbol real)
|
|
1153
|
+
\`\`\`
|
|
1154
|
+
src/
|
|
1155
|
+
├── main.ts # entry point, configura CORS y swagger
|
|
1156
|
+
├── auth/ # autenticacion JWT
|
|
1157
|
+
├── leads/ # gestion de leads
|
|
1158
|
+
└── ...
|
|
1159
|
+
\`\`\`
|
|
1160
|
+
|
|
1161
|
+
## Modulos internos
|
|
1162
|
+
| Modulo | Proposito (negocio) | Doc tecnica |
|
|
1163
|
+
|--------|---------------------|-------------|
|
|
1164
|
+
| auth | Login y validacion de tokens | modules/auth.md |
|
|
1165
|
+
| leads | Listado y consulta de negocios | modules/leads.md |
|
|
1166
|
+
|
|
1167
|
+
## Endpoints principales
|
|
1168
|
+
| Metodo | Ruta | Modulo | Que hace (lenguaje simple) |
|
|
1169
|
+
|--------|------|--------|----------------------------|
|
|
1170
|
+
| GET | /leads | leads | Lista negocios filtrables |
|
|
1171
|
+
| GET | /leads/:id | leads | Detalle de un negocio |
|
|
1172
|
+
(SOLO endpoints reales extraidos de los controllers)
|
|
1173
|
+
|
|
1174
|
+
## Variables de entorno
|
|
1175
|
+
| Variable | Default | Proposito |
|
|
1176
|
+
|----------|---------|-----------|
|
|
1177
|
+
| PORT | 8085 | Puerto del servicio |
|
|
1178
|
+
| MONGODB_URI | mongodb://... | Conexion principal |
|
|
1179
|
+
|
|
1180
|
+
## Integraciones con otros servicios / sistemas
|
|
1181
|
+
| Servicio externo | Tipo (HTTP/DB/Queue) | Proposito |
|
|
1182
|
+
|
|
1183
|
+
## Como levantar
|
|
1184
|
+
- **Instalar deps:** \`npm install\`
|
|
1185
|
+
- **Servicios necesarios:** MongoDB corriendo, security-api accesible
|
|
1186
|
+
- **Comando para iniciar:** \`npm run start:dev\`
|
|
1187
|
+
|
|
1188
|
+
## Comandos disponibles
|
|
1189
|
+
| Comando | Que hace |
|
|
1190
|
+
|
|
1191
|
+
## Decisiones tecnicas relevantes
|
|
1192
|
+
(solo si hay decisiones notables; si no, omitir esta seccion)
|
|
1193
|
+
|
|
1194
|
+
────────────────────────────────────────────────────────────────
|
|
1195
|
+
NIVEL 2 — [componente]/modules/[modulo].md
|
|
1196
|
+
Detalle de un modulo interno. Objetivo: 40-120 lineas. Crear UNO POR cada modulo significativo.
|
|
1197
|
+
────────────────────────────────────────────────────────────────
|
|
1198
|
+
Secciones obligatorias:
|
|
1199
|
+
|
|
1200
|
+
# Modulo: [nombre] — [componente]
|
|
1201
|
+
|
|
1202
|
+
## Funcion (lenguaje simple)
|
|
1203
|
+
1-2 lineas: "Permite al usuario gestionar X" / "El sistema usa este modulo para Y".
|
|
1204
|
+
|
|
1205
|
+
## Funcion (tecnica)
|
|
1206
|
+
1-2 lineas: como esta implementado a alto nivel.
|
|
1207
|
+
|
|
1208
|
+
## Flujos / casos de uso (1-3)
|
|
1209
|
+
Escenarios paso a paso. Ejemplo:
|
|
1210
|
+
- **Crear un pedido:** Cliente → POST /orders → middleware valida JWT → service.create() → guarda en DB → responde 201
|
|
1211
|
+
|
|
1212
|
+
## Endpoints / interfaces publicas (si aplica)
|
|
1213
|
+
| Metodo | Ruta | Body | Respuesta |
|
|
1214
|
+
|--------|------|------|-----------|
|
|
1215
|
+
|
|
1216
|
+
## Modelos / schemas relevantes
|
|
1217
|
+
\`\`\`typescript
|
|
1218
|
+
// shape REAL extraido del archivo de schema/model
|
|
1219
|
+
{
|
|
1220
|
+
_id: ObjectId,
|
|
1221
|
+
name: string,
|
|
1222
|
+
createdAt: Date,
|
|
1223
|
+
...
|
|
1224
|
+
}
|
|
1225
|
+
\`\`\`
|
|
1226
|
+
|
|
1227
|
+
## Reglas de negocio
|
|
1228
|
+
- Validaciones: ...
|
|
1229
|
+
- Condiciones especiales: ...
|
|
1230
|
+
- Excepciones: ...
|
|
1231
|
+
|
|
1232
|
+
## Archivos clave
|
|
1233
|
+
| Archivo | Rol |
|
|
1234
|
+
|---------|-----|
|
|
1235
|
+
| src/leads/leads.controller.ts | Endpoints /leads |
|
|
1236
|
+
| src/leads/leads.service.ts | Logica de negocio |
|
|
1237
|
+
|
|
1238
|
+
## Dependencias
|
|
1239
|
+
- **Internas (mismo componente):** auth, common
|
|
1240
|
+
- **Externas:** mongoose, class-validator
|
|
1241
|
+
- **Cross-component:** llama a security-api via HTTP para validar JWT
|
|
1242
|
+
|
|
1243
|
+
================================================================
|
|
1244
|
+
CALIBRACION DE DETALLE
|
|
1245
|
+
================================================================
|
|
1246
|
+
- NIVEL 0: 50-150 lineas (no menos, no mas)
|
|
1247
|
+
- NIVEL 1: 80-200 lineas por componente
|
|
1248
|
+
- NIVEL 2: 40-120 lineas por modulo
|
|
1249
|
+
- Si te queda corto → leiste pocos archivos, lee mas
|
|
1250
|
+
- Si te queda largo → estas repitiendo o agregando relleno, recorta
|
|
1251
|
+
|
|
1252
|
+
================================================================
|
|
1253
|
+
QUE NO HACER
|
|
1254
|
+
================================================================
|
|
1255
|
+
- NO usar "Inferido", "Probablemente", "(asumido)", "(quizas)", "parece"
|
|
1256
|
+
- NO repetir lo mismo entre niveles sin agregar valor (cada nivel zoomea mas)
|
|
1257
|
+
- NO dejar tablas vacias ni con placeholders tipo "..."
|
|
1258
|
+
- NO mezclar lenguaje tecnico puro: SIEMPRE empezar funcional, despues tecnico
|
|
1259
|
+
- NO documentar componentes triviales (scripts, docs, configs sueltas) con su propia carpeta — mencionalos en NIVEL 0 y listo
|
|
1260
|
+
- NO inventar endpoints, env vars, puertos o schemas que no leiste
|
|
1261
|
+
|
|
1262
|
+
================================================================
|
|
1263
|
+
FORMATO DE SALIDA — OBLIGATORIO
|
|
1264
|
+
================================================================
|
|
1265
|
+
Devolve UNICAMENTE bloques de archivos separados por marcadores ===. Nada de explicaciones extra fuera de los bloques.
|
|
1266
|
+
|
|
1267
|
+
=== architecture.md ===
|
|
1268
|
+
[contenido completo del NIVEL 0]
|
|
1269
|
+
|
|
1270
|
+
=== nombre-componente-1/architecture.md ===
|
|
1271
|
+
[contenido completo del NIVEL 1 del componente 1]
|
|
1272
|
+
|
|
1273
|
+
=== nombre-componente-1/modules/auth.md ===
|
|
1274
|
+
[contenido completo del NIVEL 2 del modulo auth]
|
|
1275
|
+
|
|
1276
|
+
=== nombre-componente-1/modules/leads.md ===
|
|
1277
|
+
[contenido completo del NIVEL 2 del modulo leads]
|
|
1278
|
+
|
|
1279
|
+
=== nombre-componente-2/architecture.md ===
|
|
1280
|
+
...
|
|
1281
|
+
|
|
1282
|
+
REGLAS DE PATHS:
|
|
1283
|
+
- El archivo principal SIEMPRE es: === architecture.md ===
|
|
1284
|
+
- Por componente: === [nombre-componente]/architecture.md ===
|
|
1285
|
+
- Por modulo interno: === [nombre-componente]/modules/[nombre-modulo].md ===
|
|
1286
|
+
- USA SIEMPRE rutas RELATIVAS, jamas absolutas
|
|
1287
|
+
- Documenta TODOS los componentes no triviales del DIRECTORIO_TRABAJO
|
|
1288
|
+
- Si existe documentacion previa, ACTUALIZALA preservando lo que sigue siendo valido y agregando lo nuevo`;
|
|
1016
1289
|
const res = await this.runWithFallback('explorer', prompt, 'Exploracion');
|
|
1017
1290
|
const text = extractCliText(res);
|
|
1291
|
+
// Parse sections separated by === path/file.md === markers
|
|
1292
|
+
const sections = text.split(/===\s+(.+?\.md)\s*===/).slice(1);
|
|
1293
|
+
let filesWritten = 0;
|
|
1294
|
+
for (let i = 0; i < sections.length; i += 2) {
|
|
1295
|
+
const fileName = sections[i].trim();
|
|
1296
|
+
let content = sections[i + 1] ? sections[i + 1].trim() : '';
|
|
1297
|
+
if (!content)
|
|
1298
|
+
continue;
|
|
1299
|
+
// Clean up code fences if present
|
|
1300
|
+
content = content.replace(/^```markdown\s*/i, '').replace(/^```\s*$/gm, '').trim();
|
|
1301
|
+
try {
|
|
1302
|
+
let targetPath;
|
|
1303
|
+
if (fileName === 'architecture.md' || fileName === 'ARCHITECTURE.md') {
|
|
1304
|
+
// Main architecture doc
|
|
1305
|
+
targetPath = mainArchPath;
|
|
1306
|
+
}
|
|
1307
|
+
else {
|
|
1308
|
+
// CRITICAL: Extract only the relative path components, strip any absolute path prefix
|
|
1309
|
+
// fileName could be: "datamart-data-access-api/architecture.md"
|
|
1310
|
+
// or "/home/user/project/datamart-data-access-api/architecture.md" (BAD — must ignore prefix)
|
|
1311
|
+
// We only want the components relative to contextDir
|
|
1312
|
+
// Remove any leading absolute path by extracting the last meaningful path segments
|
|
1313
|
+
const cleanName = fileName.replace(/^.*[\/\\]/, ''); // strip any prefix
|
|
1314
|
+
// Check if it's a component architecture file: "component/architecture.md" or "component/modules/mod.md"
|
|
1315
|
+
const pathParts = fileName.split(/[\/\\]/).filter(Boolean);
|
|
1316
|
+
// Keep only the last 2-3 parts (component name + file)
|
|
1317
|
+
// e.g. ["/home", "user", "proj", "datamart", "architecture.md"] → ["datamart", "architecture.md"]
|
|
1318
|
+
// e.g. ["datamart-data-access-api", "architecture.md"] → same
|
|
1319
|
+
// e.g. ["datamart-data-access-api", "modules", "config.md"] → same
|
|
1320
|
+
let relPath;
|
|
1321
|
+
if (pathParts.length >= 3 && pathParts[pathParts.length - 2] === 'modules') {
|
|
1322
|
+
// component/modules/file.md
|
|
1323
|
+
relPath = path.join(pathParts[pathParts.length - 3], 'modules', pathParts[pathParts.length - 1]);
|
|
1324
|
+
}
|
|
1325
|
+
else if (pathParts.length >= 2) {
|
|
1326
|
+
// component/file.md
|
|
1327
|
+
relPath = path.join(pathParts[pathParts.length - 2], pathParts[pathParts.length - 1]);
|
|
1328
|
+
}
|
|
1329
|
+
else {
|
|
1330
|
+
// fallback
|
|
1331
|
+
relPath = path.join('modules', cleanName);
|
|
1332
|
+
}
|
|
1333
|
+
targetPath = path.join(contextDir, relPath);
|
|
1334
|
+
}
|
|
1335
|
+
await fs.mkdir(path.dirname(targetPath), { recursive: true });
|
|
1336
|
+
await writeFile(targetPath, content);
|
|
1337
|
+
filesWritten++;
|
|
1338
|
+
}
|
|
1339
|
+
catch (err) {
|
|
1340
|
+
log.warn(`Failed to write ${fileName}: ${err.message}`);
|
|
1341
|
+
}
|
|
1342
|
+
}
|
|
1343
|
+
if (filesWritten > 0) {
|
|
1344
|
+
log.ok(`${filesWritten} documentation file(s) written`);
|
|
1345
|
+
}
|
|
1018
1346
|
// Overwrite the single last-run report (no timestamp accumulation)
|
|
1019
1347
|
try {
|
|
1020
1348
|
await writeFile(path.join(contextDir, 'explorer-last.md'), `# Explorer Report\n\nTask: ${effectiveTask}\nDate: ${new Date().toISOString()}\n\n${text}\n`);
|
|
@@ -1048,16 +1376,47 @@ PROYECTO: ${this.config.project}
|
|
|
1048
1376
|
${existingArch ? `DOCUMENTACION EXISTENTE:\n${existingArch.slice(0, 3000)}\n` : 'Sin documentacion previa.\n'}
|
|
1049
1377
|
CONTEXTO: ${context.slice(0, 2000)}
|
|
1050
1378
|
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
-
|
|
1379
|
+
OBJETIVO
|
|
1380
|
+
Generar documentacion ESCALONADA en 3 niveles (global / componente / modulo), util para perfiles funcionales y tecnicos. Detallada pero concreta. Cero suposiciones.
|
|
1381
|
+
|
|
1382
|
+
REGLA DE ORO — PROHIBIDO ESPECULAR
|
|
1383
|
+
NO uses "Inferido", "Probablemente", "Asumido", "(quizas)", "parece". Si no podes verificar un dato leyendo un archivo, OMITELO. Cada puerto, version, endpoint, env var y schema debe haber sido leido de un archivo real.
|
|
1384
|
+
|
|
1385
|
+
WORKFLOW (usa tus tools)
|
|
1386
|
+
1. Lista DIRECTORIO_TRABAJO y detecta los componentes no triviales.
|
|
1387
|
+
2. Para cada componente, LEE como minimo:
|
|
1388
|
+
- Manifest (package.json | requirements.txt | pom.xml | build.gradle | go.mod | Cargo.toml)
|
|
1389
|
+
- Entry point (main.ts | index.js | app.py | Application.java | cmd/main.go)
|
|
1390
|
+
- .env / .env.example / application.yml / settings.py
|
|
1391
|
+
- 2-3 controladores/routers principales (para endpoints reales)
|
|
1392
|
+
- 2-3 schemas/models principales (para shapes reales)
|
|
1393
|
+
3. Mapea relaciones reales (URLs en .env, imports cross-component, conexiones a DB/colas).
|
|
1394
|
+
|
|
1395
|
+
LENGUAJE DUAL
|
|
1396
|
+
Cada seccion abre con UNA linea simple (que entienda un PM). Despues, el detalle tecnico.
|
|
1397
|
+
- MAL: "El microservicio orquesta la persistencia mediante un repositorio."
|
|
1398
|
+
- BIEN: "Guarda la informacion del usuario. Internamente usa un repositorio que abstrae MongoDB."
|
|
1399
|
+
|
|
1400
|
+
NIVEL 0 — ${archPath} (50-150 lineas)
|
|
1401
|
+
Secciones: Overview funcional · Tabla de componentes (con stack+version, puerto, proposito, entry point) · Tabla de relaciones (origen, destino, tipo, proposito) · Diagrama ASCII · 3-5 flujos end-to-end (mapping funcional → tecnico) · Prerequisitos · Comandos globales.
|
|
1402
|
+
|
|
1403
|
+
NIVEL 1 — ${contextDir}/<componente>/architecture.md (80-200 lineas, uno por componente)
|
|
1404
|
+
Secciones: Que hace (simple) · Casos de uso (3-6 bullets en formato negocio) · Stack tecnico (tabla con versiones reales) · Puerto y URLs · Estructura interna (arbol real) · Tabla de modulos · Tabla de endpoints reales · Tabla de variables de entorno · Integraciones · Como levantar · Comandos.
|
|
1405
|
+
|
|
1406
|
+
NIVEL 2 — ${contextDir}/<componente>/modules/<modulo>.md (40-120 lineas, uno por modulo significativo)
|
|
1407
|
+
Secciones: Funcion (simple) · Funcion (tecnica) · Flujos / casos de uso paso a paso · Endpoints / interfaces · Schemas reales en codigo · Reglas de negocio · Archivos clave · Dependencias internas/externas/cross-component.
|
|
1408
|
+
|
|
1409
|
+
CALIBRACION
|
|
1410
|
+
- NIVEL 0: 50-150 lineas. NIVEL 1: 80-200 por componente. NIVEL 2: 40-120 por modulo.
|
|
1411
|
+
- Si te queda corto, leiste pocos archivos. Si te queda largo, recorta el relleno.
|
|
1412
|
+
- Componentes triviales (solo scripts/docs): mencionalos en NIVEL 0 y NO les crees subcarpeta.
|
|
1413
|
+
|
|
1414
|
+
REGLAS DE ESCRITURA
|
|
1415
|
+
- Solo podes escribir archivos dentro de ${contextDir}/
|
|
1416
|
+
- NO crees archivos sueltos en la raiz de ${contextDir}/ (excepto architecture.md)
|
|
1417
|
+
- Por componente usa: ${contextDir}/<componente>/architecture.md
|
|
1418
|
+
- Por modulo usa: ${contextDir}/<componente>/modules/<modulo>.md
|
|
1419
|
+
- Si existe documentacion previa, ACTUALIZALA preservando lo valido`);
|
|
1061
1420
|
let result;
|
|
1062
1421
|
const sp = this._startSpinner(`agent-explorer ${role.model}`);
|
|
1063
1422
|
try {
|
package/dist/index.js
CHANGED
|
@@ -38,6 +38,7 @@ program
|
|
|
38
38
|
.option('--model [cli]', 'Alias for --models')
|
|
39
39
|
.option('--reset-coordinator', 'Clear coordinator selection (re-pick on next run)')
|
|
40
40
|
.option('--reset-auth', 'Wipe all auth credentials and start fresh')
|
|
41
|
+
.option('--usage', 'Check quota status for all role CLIs')
|
|
41
42
|
.addHelpText('after', `
|
|
42
43
|
Setup subcommands:
|
|
43
44
|
agent-mp setup init Full interactive wizard (login + roles + project)
|
|
@@ -131,6 +132,22 @@ if (nativeRole) {
|
|
|
131
132
|
}
|
|
132
133
|
process.exit(0);
|
|
133
134
|
}
|
|
135
|
+
// --usage: check quota status with test API call
|
|
136
|
+
if (args.includes('--usage') || args.includes('usage')) {
|
|
137
|
+
const { qwenUsage, getTokenPath } = await import('./utils/qwen-auth.js');
|
|
138
|
+
const result = await qwenUsage();
|
|
139
|
+
const exp = result.token ? new Date(result.token.expiresAt).toLocaleString() : 'N/A';
|
|
140
|
+
if (result.ok) {
|
|
141
|
+
console.log(chalk.green(` ✓ ${PKG_NAME}: quota OK`));
|
|
142
|
+
console.log(chalk.dim(` Token expires: ${exp}`));
|
|
143
|
+
console.log(chalk.dim(` Model: ${result.model}`));
|
|
144
|
+
}
|
|
145
|
+
else {
|
|
146
|
+
console.log(chalk.red(` ✗ ${PKG_NAME}: ${result.error}`));
|
|
147
|
+
console.log(chalk.dim(` Token expires: ${exp}`));
|
|
148
|
+
}
|
|
149
|
+
process.exit(result.ok ? 0 : 1);
|
|
150
|
+
}
|
|
134
151
|
// Parse --model flag if provided
|
|
135
152
|
const modelIdx = args.findIndex(a => a === '--model' || a === '-m');
|
|
136
153
|
let model;
|
|
@@ -150,6 +167,23 @@ if (nativeRole) {
|
|
|
150
167
|
}
|
|
151
168
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
152
169
|
program.action(async (task, options) => {
|
|
170
|
+
// --usage: check quota status
|
|
171
|
+
if (options.usage) {
|
|
172
|
+
const { qwenUsage } = await import('./utils/qwen-auth.js');
|
|
173
|
+
const { PKG_NAME } = await import('./utils/config.js');
|
|
174
|
+
const result = await qwenUsage();
|
|
175
|
+
const exp = result.token ? new Date(result.token.expiresAt).toLocaleString() : 'N/A';
|
|
176
|
+
if (result.ok) {
|
|
177
|
+
console.log(chalk.green(` ✓ ${PKG_NAME}: quota OK`));
|
|
178
|
+
console.log(chalk.dim(` Token expires: ${exp}`));
|
|
179
|
+
console.log(chalk.dim(` Model: ${result.model}`));
|
|
180
|
+
}
|
|
181
|
+
else {
|
|
182
|
+
console.log(chalk.red(` ✗ ${PKG_NAME}: ${result.error}`));
|
|
183
|
+
console.log(chalk.dim(` Token expires: ${exp}`));
|
|
184
|
+
}
|
|
185
|
+
process.exit(result.ok ? 0 : 1);
|
|
186
|
+
}
|
|
153
187
|
if (options.resetCoordinator || options.resetAuth) {
|
|
154
188
|
const authFile = path.join(AGENT_HOME, 'auth.json');
|
|
155
189
|
const qwenCreds = path.join(AGENT_HOME, 'oauth_creds.json');
|
package/dist/utils/config.d.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { AgentConfig } from '../types.js';
|
|
2
2
|
/**
|
|
3
3
|
* Resolve the actual project root starting from a given directory.
|
|
4
|
-
* If .agent/config.json exists in the start dir,
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* If .agent/config.json exists in the start dir, use it directly
|
|
5
|
+
* (the user already configured this as the project root).
|
|
6
|
+
* If not, walk up the directory tree looking for .agent/config.json.
|
|
7
|
+
* Returns the resolved project root.
|
|
7
8
|
*/
|
|
8
9
|
export declare function resolveProjectDir(startDir: string): Promise<string>;
|
|
9
10
|
export declare const PKG_NAME: string;
|
package/dist/utils/config.js
CHANGED
|
@@ -3,46 +3,29 @@ import * as fsSync from 'fs';
|
|
|
3
3
|
import * as path from 'path';
|
|
4
4
|
import * as os from 'os';
|
|
5
5
|
const KNOWN_PKGS = ['agent-mp', 'agent-orch', 'agent-impl', 'agent-rev', 'agent-explorer'];
|
|
6
|
-
// Files/dirs that indicate a directory is the real project root
|
|
7
|
-
const PROJECT_INDICATORS = [
|
|
8
|
-
'package.json', 'pyproject.toml', 'requirements.txt', 'setup.py', 'setup.cfg',
|
|
9
|
-
'Cargo.toml', 'go.mod', 'Gemfile', 'pom.xml', 'build.gradle', 'build.gradle.kts',
|
|
10
|
-
'CMakeLists.txt', 'Makefile', 'composer.json', 'mix.exs', 'pubspec.yaml',
|
|
11
|
-
'src', 'app', 'lib', 'main.py', 'app.py', 'index.js', 'index.ts',
|
|
12
|
-
'main.go', 'main.rs', 'lib.rs', 'app.js', 'app.ts',
|
|
13
|
-
];
|
|
14
6
|
/**
|
|
15
7
|
* Resolve the actual project root starting from a given directory.
|
|
16
|
-
* If .agent/config.json exists in the start dir,
|
|
17
|
-
*
|
|
18
|
-
*
|
|
8
|
+
* If .agent/config.json exists in the start dir, use it directly
|
|
9
|
+
* (the user already configured this as the project root).
|
|
10
|
+
* If not, walk up the directory tree looking for .agent/config.json.
|
|
11
|
+
* Returns the resolved project root.
|
|
19
12
|
*/
|
|
20
13
|
export async function resolveProjectDir(startDir) {
|
|
21
|
-
|
|
22
|
-
if (
|
|
14
|
+
// If .agent/config.json exists in current dir, use it — user already configured this
|
|
15
|
+
if (await fileExists(path.join(startDir, '.agent', 'config.json'))) {
|
|
23
16
|
return startDir;
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
let
|
|
27
|
-
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
continue;
|
|
32
|
-
const subDir = path.join(startDir, entry.name);
|
|
33
|
-
let score = 0;
|
|
34
|
-
for (const indicator of PROJECT_INDICATORS) {
|
|
35
|
-
if (await fileExists(path.join(subDir, indicator)))
|
|
36
|
-
score++;
|
|
37
|
-
}
|
|
38
|
-
if (score > bestScore) {
|
|
39
|
-
bestScore = score;
|
|
40
|
-
bestDir = subDir;
|
|
41
|
-
}
|
|
17
|
+
}
|
|
18
|
+
// Walk up the directory tree looking for .agent/config.json
|
|
19
|
+
let current = startDir;
|
|
20
|
+
while (current !== path.dirname(current)) {
|
|
21
|
+
const parent = path.dirname(current);
|
|
22
|
+
if (await fileExists(path.join(parent, '.agent', 'config.json'))) {
|
|
23
|
+
return parent;
|
|
42
24
|
}
|
|
25
|
+
current = parent;
|
|
43
26
|
}
|
|
44
|
-
|
|
45
|
-
return
|
|
27
|
+
// No .agent/config.json found — return original dir (will fail gracefully)
|
|
28
|
+
return startDir;
|
|
46
29
|
}
|
|
47
30
|
async function fileExists(p) {
|
|
48
31
|
try {
|
|
@@ -2,6 +2,13 @@
|
|
|
2
2
|
export declare const QWEN_AGENT_HOME: string;
|
|
3
3
|
/** Dynamic getter (recommended for runtime changes) */
|
|
4
4
|
export declare function getQwenHome(): string;
|
|
5
|
+
interface QwenToken {
|
|
6
|
+
accessToken: string;
|
|
7
|
+
refreshToken: string;
|
|
8
|
+
expiresAt: number;
|
|
9
|
+
idToken?: string;
|
|
10
|
+
resourceUrl?: string;
|
|
11
|
+
}
|
|
5
12
|
export declare function getTokenPath(): Promise<string>;
|
|
6
13
|
export declare function qwenLogin(): Promise<boolean>;
|
|
7
14
|
export declare function qwenAuthStatus(): Promise<{
|
|
@@ -20,3 +27,11 @@ export declare function callQwenAPI(prompt: string, model?: string, onData?: (ch
|
|
|
20
27
|
* Calls the Qwen REST API directly — no dependency on any qwen CLI binary.
|
|
21
28
|
*/
|
|
22
29
|
export declare function callQwenAPIFromCreds(prompt: string, model: string, credsPath: string, onData?: (chunk: string) => void): Promise<string>;
|
|
30
|
+
/** Check quota status by making a minimal test API call */
|
|
31
|
+
export declare function qwenUsage(credsPath?: string): Promise<{
|
|
32
|
+
ok: boolean;
|
|
33
|
+
token: QwenToken | null;
|
|
34
|
+
error?: string;
|
|
35
|
+
model?: string;
|
|
36
|
+
}>;
|
|
37
|
+
export {};
|
package/dist/utils/qwen-auth.js
CHANGED
|
@@ -286,9 +286,14 @@ async function callQwenAPIWithToken(token, prompt, model, onData) {
|
|
|
286
286
|
});
|
|
287
287
|
if (!response.ok) {
|
|
288
288
|
const errorText = await response.text();
|
|
289
|
-
|
|
289
|
+
// 401 = auth error, refresh may help
|
|
290
|
+
if (response.status === 401) {
|
|
290
291
|
throw new Error(`QWEN_AUTH_EXPIRED: ${errorText}`);
|
|
291
292
|
}
|
|
293
|
+
// 429 insufficient_quota = daily quota exhausted, refresh won't help
|
|
294
|
+
if (response.status === 429 && errorText.includes('insufficient_quota')) {
|
|
295
|
+
throw new Error(`QWEN_QUOTA_EXCEEDED: ${errorText}`);
|
|
296
|
+
}
|
|
292
297
|
throw new Error(`Qwen API error: ${response.status} - ${errorText}`);
|
|
293
298
|
}
|
|
294
299
|
if (!useStream) {
|
|
@@ -342,6 +347,9 @@ export async function callQwenAPI(prompt, model = 'coder-model', onData) {
|
|
|
342
347
|
return await callQwenAPIWithToken(token, prompt, model, onData);
|
|
343
348
|
}
|
|
344
349
|
catch (err) {
|
|
350
|
+
// Quota errors: refresh won't help, propagate immediately
|
|
351
|
+
if (err.message?.startsWith('QWEN_QUOTA_EXCEEDED'))
|
|
352
|
+
throw err;
|
|
345
353
|
if (!err.message?.startsWith('QWEN_AUTH_EXPIRED'))
|
|
346
354
|
throw err;
|
|
347
355
|
if (token.refreshToken) {
|
|
@@ -390,6 +398,9 @@ export async function callQwenAPIFromCreds(prompt, model, credsPath, onData) {
|
|
|
390
398
|
return await callQwenAPIWithToken(token, prompt, model, onData);
|
|
391
399
|
}
|
|
392
400
|
catch (err) {
|
|
401
|
+
// Quota errors: refresh won't help, propagate immediately
|
|
402
|
+
if (err.message?.startsWith('QWEN_QUOTA_EXCEEDED'))
|
|
403
|
+
throw err;
|
|
393
404
|
if (!err.message?.startsWith('QWEN_AUTH_EXPIRED'))
|
|
394
405
|
throw err;
|
|
395
406
|
if (token.refreshToken) {
|
|
@@ -402,3 +413,80 @@ export async function callQwenAPIFromCreds(prompt, model, credsPath, onData) {
|
|
|
402
413
|
throw new Error(`QWEN_AUTH_EXPIRED: Sesión expirada. Ejecutá: ${cliName} --login`);
|
|
403
414
|
}
|
|
404
415
|
}
|
|
416
|
+
/** Check quota status by making a minimal test API call */
|
|
417
|
+
export async function qwenUsage(credsPath) {
|
|
418
|
+
let token;
|
|
419
|
+
if (credsPath) {
|
|
420
|
+
try {
|
|
421
|
+
const raw = JSON.parse(await fs.readFile(credsPath, 'utf-8'));
|
|
422
|
+
token = {
|
|
423
|
+
accessToken: raw.accessToken || raw.access_token || '',
|
|
424
|
+
refreshToken: raw.refreshToken || raw.refresh_token || '',
|
|
425
|
+
expiresAt: raw.expiresAt || raw.expiry_date || 0,
|
|
426
|
+
idToken: raw.idToken || raw.id_token,
|
|
427
|
+
resourceUrl: raw.resourceUrl || raw.resource_url,
|
|
428
|
+
};
|
|
429
|
+
}
|
|
430
|
+
catch {
|
|
431
|
+
return { ok: false, token: null, error: 'No credentials found' };
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
else {
|
|
435
|
+
token = await loadToken();
|
|
436
|
+
}
|
|
437
|
+
if (!token || !token.accessToken) {
|
|
438
|
+
return { ok: false, token, error: 'No access token — run --login' };
|
|
439
|
+
}
|
|
440
|
+
if (token.expiresAt < Date.now()) {
|
|
441
|
+
// Try refresh
|
|
442
|
+
if (token.refreshToken) {
|
|
443
|
+
const refreshed = await doRefreshToken(token.refreshToken);
|
|
444
|
+
if (refreshed) {
|
|
445
|
+
token = refreshed;
|
|
446
|
+
if (!credsPath)
|
|
447
|
+
await saveToken(refreshed);
|
|
448
|
+
else
|
|
449
|
+
await fs.writeFile(credsPath, JSON.stringify(refreshed, null, 2), 'utf-8');
|
|
450
|
+
}
|
|
451
|
+
else {
|
|
452
|
+
return { ok: false, token, error: 'Token expired and refresh failed' };
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
else {
|
|
456
|
+
return { ok: false, token, error: 'Token expired, no refresh token' };
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
// Make a minimal test call to check quota
|
|
460
|
+
try {
|
|
461
|
+
const result = await callQwenAPIWithToken(token, 'ping', 'coder-model');
|
|
462
|
+
return { ok: true, token, model: 'coder-model' };
|
|
463
|
+
}
|
|
464
|
+
catch (err) {
|
|
465
|
+
// If auth failed, try refresh even if expiresAt hasn't passed yet
|
|
466
|
+
if (err.message?.startsWith('QWEN_AUTH_EXPIRED') && token.refreshToken) {
|
|
467
|
+
const refreshed = await doRefreshToken(token.refreshToken);
|
|
468
|
+
if (refreshed) {
|
|
469
|
+
token = refreshed;
|
|
470
|
+
if (!credsPath)
|
|
471
|
+
await saveToken(refreshed);
|
|
472
|
+
else
|
|
473
|
+
await fs.writeFile(credsPath, JSON.stringify(refreshed, null, 2), 'utf-8');
|
|
474
|
+
// Retry with new token
|
|
475
|
+
try {
|
|
476
|
+
await callQwenAPIWithToken(refreshed, 'ping', 'coder-model');
|
|
477
|
+
return { ok: true, token: refreshed, model: 'coder-model' };
|
|
478
|
+
}
|
|
479
|
+
catch (err2) {
|
|
480
|
+
if (err2.message?.startsWith('QWEN_QUOTA_EXCEEDED')) {
|
|
481
|
+
return { ok: false, token: refreshed, error: 'Daily quota exhausted — wait or --login with different account' };
|
|
482
|
+
}
|
|
483
|
+
return { ok: false, token: refreshed, error: err2.message };
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
if (err.message?.startsWith('QWEN_QUOTA_EXCEEDED')) {
|
|
488
|
+
return { ok: false, token, error: 'Daily quota exhausted — wait or --login with different account' };
|
|
489
|
+
}
|
|
490
|
+
return { ok: false, token, error: err.message };
|
|
491
|
+
}
|
|
492
|
+
}
|
package/package.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"name":"agent-rev","version":"0.5.
|
|
1
|
+
{"name":"agent-rev","version":"0.5.12","description":"Deterministic multi-agent CLI orchestrator — plan, code, review","type":"module","main":"./dist/index.js","files":["dist/"],"bin":{"agent-rev":"dist/index.js"},"scripts":{"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","dev":"tsx src/index.ts","prepublishOnly":"npm run build"},"keywords":["ai","agent","orchestrator","multi-agent","cli","coding"],"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"},"devDependencies":{"@types/node":"^22.13.0","@types/open":"^6.1.0","tsx":"^4.19.3","typescript":"^5.7.3"},"engines":{"node":">=18.0.0"}}
|