agent-rev 0.3.6 → 0.4.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 +1 -29
- package/dist/core/engine.d.ts +12 -0
- package/dist/core/engine.js +148 -13
- package/dist/ui/input.d.ts +20 -3
- package/dist/ui/input.js +148 -50
- package/dist/utils/qwen-auth.d.ts +2 -2
- package/dist/utils/qwen-auth.js +26 -18
- package/package.json +44 -1
package/dist/commands/repl.js
CHANGED
|
@@ -716,12 +716,10 @@ function cmdHelp(fi) {
|
|
|
716
716
|
{ key: '/setup explorer', value: 'Reconfigure explorer only' },
|
|
717
717
|
{ key: '/config-multi', value: 'Reconfigure all agents at once' },
|
|
718
718
|
{ key: '/status', value: 'Show current configuration and tasks' },
|
|
719
|
-
{ key: '/explorer <task>', value: 'Run explorer agent on a task/question' },
|
|
720
719
|
{ key: '/run <task>', value: 'Full cycle: orchestrator → implementor → reviewer' },
|
|
721
720
|
{ key: '/run orch <task>', value: 'Run only orchestrator' },
|
|
722
721
|
{ key: '/run impl <id>', value: 'Run only implementor' },
|
|
723
722
|
{ key: '/run rev <id>', value: 'Run only reviewer' },
|
|
724
|
-
{ key: '/run explorer <task>', value: 'Run only explorer' },
|
|
725
723
|
{ key: '/models', value: 'List models for all installed CLIs' },
|
|
726
724
|
{ key: '/models <cli>', value: 'List models for a specific CLI' },
|
|
727
725
|
{ key: '/login', value: 'Login (Qwen OAuth or CLI auth)' },
|
|
@@ -1037,12 +1035,6 @@ export async function runRepl(resumeSession) {
|
|
|
1037
1035
|
const progress = await readJson(path.join(taskDir, 'progress.json'));
|
|
1038
1036
|
await engine.runReviewer(taskId, plan, progress);
|
|
1039
1037
|
}
|
|
1040
|
-
else if (args[0] === 'explorer' || args[0] === 'exp') {
|
|
1041
|
-
const task = args.slice(1).join(' ') || undefined;
|
|
1042
|
-
const engine = new AgentEngine(config, dir, gCoordinatorCmd, rl, fi, handleCmd);
|
|
1043
|
-
const result = await engine.runExplorer(task);
|
|
1044
|
-
fi.println(result);
|
|
1045
|
-
}
|
|
1046
1038
|
else {
|
|
1047
1039
|
const task = args.join(' ');
|
|
1048
1040
|
const engine = new AgentEngine(config, dir, gCoordinatorCmd, rl, fi, handleCmd);
|
|
@@ -1050,20 +1042,6 @@ export async function runRepl(resumeSession) {
|
|
|
1050
1042
|
}
|
|
1051
1043
|
break;
|
|
1052
1044
|
}
|
|
1053
|
-
case 'explorer': {
|
|
1054
|
-
const task = args.join(' ') || undefined;
|
|
1055
|
-
try {
|
|
1056
|
-
const dir = process.cwd();
|
|
1057
|
-
const config = await loadProjectConfig(dir);
|
|
1058
|
-
const engine = new AgentEngine(config, dir, gCoordinatorCmd, rl, fi, handleCmd);
|
|
1059
|
-
const result = await engine.runExplorer(task);
|
|
1060
|
-
fi.println(result);
|
|
1061
|
-
}
|
|
1062
|
-
catch (err) {
|
|
1063
|
-
fi.println(chalk.red(` Explorer error: ${err.message}`));
|
|
1064
|
-
}
|
|
1065
|
-
break;
|
|
1066
|
-
}
|
|
1067
1045
|
case 'models':
|
|
1068
1046
|
case 'model':
|
|
1069
1047
|
await withRl((rl) => cmdModels(args[0], fi, rl));
|
|
@@ -1225,12 +1203,6 @@ export async function runRole(role, arg, model) {
|
|
|
1225
1203
|
await engine.runReviewer(arg, plan, progress);
|
|
1226
1204
|
break;
|
|
1227
1205
|
}
|
|
1228
|
-
case 'explorer':
|
|
1229
|
-
case 'exp': {
|
|
1230
|
-
const result = await engine.runExplorer(arg || undefined);
|
|
1231
|
-
console.log(result);
|
|
1232
|
-
break;
|
|
1233
|
-
}
|
|
1234
1206
|
case 'coordinator':
|
|
1235
1207
|
case 'coord': {
|
|
1236
1208
|
console.log(chalk.yellow(' Coordinator mode requires interactive REPL.'));
|
|
@@ -1238,7 +1210,7 @@ export async function runRole(role, arg, model) {
|
|
|
1238
1210
|
process.exit(1);
|
|
1239
1211
|
}
|
|
1240
1212
|
default:
|
|
1241
|
-
console.log(chalk.red(` Unknown role: ${role}. Use: orchestrator, implementor, reviewer
|
|
1213
|
+
console.log(chalk.red(` Unknown role: ${role}. Use: orchestrator, implementor, reviewer`));
|
|
1242
1214
|
process.exit(1);
|
|
1243
1215
|
}
|
|
1244
1216
|
}
|
package/dist/core/engine.d.ts
CHANGED
|
@@ -16,6 +16,10 @@ export declare class AgentEngine {
|
|
|
16
16
|
private totalTokens;
|
|
17
17
|
private phaseTokens;
|
|
18
18
|
constructor(config: AgentConfig, projectDir: string, coordinatorCmd?: string, rl?: readline.Interface, fi?: FixedInput, slashHandler?: SlashHandler);
|
|
19
|
+
/** Start the activity box for a subagent call. Returns { stop, push }. */
|
|
20
|
+
private _startSpinner;
|
|
21
|
+
/** Extract readable text lines from a qwen/CLI streaming chunk. */
|
|
22
|
+
private _parseChunk;
|
|
19
23
|
/**
|
|
20
24
|
* FASE 0 — Clarificacion con el programador.
|
|
21
25
|
* El coordinador (CLI activo, ej: Qwen) conversa con el usuario
|
|
@@ -38,6 +42,14 @@ export declare class AgentEngine {
|
|
|
38
42
|
runReviewer(taskId: string, plan: TaskPlan, progress: TaskProgress): Promise<{
|
|
39
43
|
verdict: string;
|
|
40
44
|
}>;
|
|
45
|
+
/**
|
|
46
|
+
* Scan contextDir for stray files (outside the allowed structure) and clean them up:
|
|
47
|
+
* - Old timestamped explorer-*.md reports → delete (content already in architecture.md)
|
|
48
|
+
* - Other stray .md files at root level → append content to architecture.md, then delete
|
|
49
|
+
* Allowed root-level files: architecture.md, explorer-last.md
|
|
50
|
+
* Allowed subdirectory files: <service>/architecture.md (not touched here)
|
|
51
|
+
*/
|
|
52
|
+
private _cleanContextDir;
|
|
41
53
|
runExplorer(task?: string): Promise<string>;
|
|
42
54
|
/** Called when the current binary IS the configured explorer CLI (prevents recursion).
|
|
43
55
|
* Builds the full exploration prompt and calls Qwen API using own credentials. */
|
package/dist/core/engine.js
CHANGED
|
@@ -219,6 +219,55 @@ export class AgentEngine {
|
|
|
219
219
|
this.fi = fi;
|
|
220
220
|
this.slashHandler = slashHandler;
|
|
221
221
|
}
|
|
222
|
+
/** Start the activity box for a subagent call. Returns { stop, push }. */
|
|
223
|
+
_startSpinner(label) {
|
|
224
|
+
const noop = { stop() { }, push(_) { } };
|
|
225
|
+
if (!this.fi)
|
|
226
|
+
return noop;
|
|
227
|
+
const frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
228
|
+
let i = 0;
|
|
229
|
+
const t0 = Date.now();
|
|
230
|
+
const fi = this.fi;
|
|
231
|
+
fi.startActivity(`${frames[0]} ${label} 0s`);
|
|
232
|
+
const iv = setInterval(() => {
|
|
233
|
+
const s = Math.floor((Date.now() - t0) / 1000);
|
|
234
|
+
fi.updateActivityHeader(`${frames[i++ % frames.length]} ${label} ${s}s`);
|
|
235
|
+
}, 100);
|
|
236
|
+
return {
|
|
237
|
+
stop() { clearInterval(iv); fi.stopActivity(); },
|
|
238
|
+
push(line) { fi.pushActivity(line); },
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
/** Extract readable text lines from a qwen/CLI streaming chunk. */
|
|
242
|
+
_parseChunk(chunk) {
|
|
243
|
+
const out = [];
|
|
244
|
+
for (const raw of chunk.split('\n')) {
|
|
245
|
+
const line = raw.trim();
|
|
246
|
+
if (!line)
|
|
247
|
+
continue;
|
|
248
|
+
// Try to extract text from JSON streaming events
|
|
249
|
+
if (line.startsWith('{')) {
|
|
250
|
+
try {
|
|
251
|
+
const obj = JSON.parse(line);
|
|
252
|
+
const text = obj.result ||
|
|
253
|
+
obj.data?.content ||
|
|
254
|
+
obj.message?.content?.[0]?.text ||
|
|
255
|
+
obj.choices?.[0]?.delta?.content ||
|
|
256
|
+
obj.choices?.[0]?.message?.content ||
|
|
257
|
+
'';
|
|
258
|
+
if (text.trim())
|
|
259
|
+
out.push(text.trim());
|
|
260
|
+
continue;
|
|
261
|
+
}
|
|
262
|
+
catch { /* not JSON — fall through */ }
|
|
263
|
+
}
|
|
264
|
+
// Skip lines that look like SSE metadata
|
|
265
|
+
if (line.startsWith('data:') || line.startsWith('event:') || line === '[DONE]')
|
|
266
|
+
continue;
|
|
267
|
+
out.push(line);
|
|
268
|
+
}
|
|
269
|
+
return out;
|
|
270
|
+
}
|
|
222
271
|
/**
|
|
223
272
|
* FASE 0 — Clarificacion con el programador.
|
|
224
273
|
* El coordinador (CLI activo, ej: Qwen) conversa con el usuario
|
|
@@ -253,11 +302,15 @@ INSTRUCCIONES:
|
|
|
253
302
|
if (this.coordinatorCmd.startsWith('qwen')) {
|
|
254
303
|
// Use Qwen API directly — avoids the qwen CLI's own OAuth flow
|
|
255
304
|
// which causes mid-session auth popups and breaks display.
|
|
305
|
+
const model = this.coordinatorCmd.match(/(?:-m|--model)\s+(\S+)/)?.[1] || 'coder-model';
|
|
306
|
+
const sp = this._startSpinner(`coordinador ${model}`);
|
|
256
307
|
try {
|
|
257
|
-
const
|
|
258
|
-
|
|
308
|
+
const result = await callQwenAPI(prompt, model, (c) => this._parseChunk(c).forEach(l => sp.push(l)));
|
|
309
|
+
sp.stop();
|
|
310
|
+
return result;
|
|
259
311
|
}
|
|
260
312
|
catch (err) {
|
|
313
|
+
sp.stop();
|
|
261
314
|
if (err.message?.startsWith('QWEN_AUTH_EXPIRED')) {
|
|
262
315
|
console.log(chalk.red('\n ✗ Sesión Qwen expirada.'));
|
|
263
316
|
console.log(chalk.yellow(' Ejecutá: /login para re-autenticarte.\n'));
|
|
@@ -269,7 +322,9 @@ INSTRUCCIONES:
|
|
|
269
322
|
}
|
|
270
323
|
}
|
|
271
324
|
else {
|
|
325
|
+
const sp = this._startSpinner(`coordinador`);
|
|
272
326
|
res = await runCli(this.coordinatorCmd, prompt, 600000, envOverride);
|
|
327
|
+
sp.stop();
|
|
273
328
|
}
|
|
274
329
|
// Extract readable text — search for JSON array even if there's prefix text
|
|
275
330
|
let responseText = res.output.trim();
|
|
@@ -366,15 +421,19 @@ INSTRUCCIONES:
|
|
|
366
421
|
const rolePrompt = this.buildRolePrompt(roleName, prompt);
|
|
367
422
|
/** Try a cmd, and if it fails, auto-detect flags from --help and retry */
|
|
368
423
|
const tryWithAutoRepair = async (cliName, model, currentCmd) => {
|
|
424
|
+
const sp = this._startSpinner(`${cliName} ${model}`);
|
|
369
425
|
try {
|
|
370
426
|
const result = await runCli(currentCmd, rolePrompt);
|
|
371
427
|
if (result.exitCode !== 0) {
|
|
428
|
+
sp.stop();
|
|
372
429
|
const detail = result.output.trim().slice(0, 500);
|
|
373
430
|
throw new Error(`${cliName} exited with code ${result.exitCode}${detail ? `\n${detail}` : ''}`);
|
|
374
431
|
}
|
|
432
|
+
sp.stop();
|
|
375
433
|
return result.output;
|
|
376
434
|
}
|
|
377
435
|
catch (err) {
|
|
436
|
+
sp.stop();
|
|
378
437
|
log.warn(`${cliName} failed, detecting flags from --help: ${err.message}`);
|
|
379
438
|
const detected = detectCliFlags(cliName);
|
|
380
439
|
if (Object.keys(detected).length === 0) {
|
|
@@ -426,8 +485,10 @@ INSTRUCCIONES:
|
|
|
426
485
|
// Config file might not exist yet
|
|
427
486
|
}
|
|
428
487
|
// Retry with new command
|
|
488
|
+
const sp2 = this._startSpinner(`${cliName} ${model} (retry)`);
|
|
429
489
|
try {
|
|
430
490
|
const result = await runCli(newCmd, rolePrompt);
|
|
491
|
+
sp2.stop();
|
|
431
492
|
if (result.exitCode !== 0) {
|
|
432
493
|
const detail = result.output.trim().slice(0, 500);
|
|
433
494
|
throw new Error(`${cliName} (repaired) exited with code ${result.exitCode}${detail ? `\n${detail}` : ''}`);
|
|
@@ -436,6 +497,7 @@ INSTRUCCIONES:
|
|
|
436
497
|
return result.output;
|
|
437
498
|
}
|
|
438
499
|
catch (retryErr) {
|
|
500
|
+
sp2.stop();
|
|
439
501
|
log.warn(`Repaired ${cliName} also failed: ${retryErr.message}`);
|
|
440
502
|
return null;
|
|
441
503
|
}
|
|
@@ -452,11 +514,15 @@ INSTRUCCIONES:
|
|
|
452
514
|
log.warn(`${cliName} has no credentials — run: ${cliName} --login`);
|
|
453
515
|
return null;
|
|
454
516
|
}
|
|
517
|
+
const sp = this._startSpinner(`${cliName} ${model}`);
|
|
455
518
|
try {
|
|
456
519
|
log.info(`${cliName}: calling Qwen API with own credentials (${model})`);
|
|
457
|
-
|
|
520
|
+
const result = await callQwenAPIFromCreds(rolePrompt, model, credsPath, (c) => this._parseChunk(c).forEach(l => sp.push(l)));
|
|
521
|
+
sp.stop();
|
|
522
|
+
return result;
|
|
458
523
|
}
|
|
459
524
|
catch (err) {
|
|
525
|
+
sp.stop();
|
|
460
526
|
if (err.message?.startsWith('QWEN_AUTH_EXPIRED')) {
|
|
461
527
|
console.log(chalk.red(`\n ✗ Sesión expirada para ${cliName}.`));
|
|
462
528
|
console.log(chalk.yellow(` Ejecutá: ${cliName} --login\n`));
|
|
@@ -768,6 +834,66 @@ INSTRUCCIONES:
|
|
|
768
834
|
log.verdict(verdict);
|
|
769
835
|
return { verdict };
|
|
770
836
|
}
|
|
837
|
+
/**
|
|
838
|
+
* Scan contextDir for stray files (outside the allowed structure) and clean them up:
|
|
839
|
+
* - Old timestamped explorer-*.md reports → delete (content already in architecture.md)
|
|
840
|
+
* - Other stray .md files at root level → append content to architecture.md, then delete
|
|
841
|
+
* Allowed root-level files: architecture.md, explorer-last.md
|
|
842
|
+
* Allowed subdirectory files: <service>/architecture.md (not touched here)
|
|
843
|
+
*/
|
|
844
|
+
async _cleanContextDir(contextDir) {
|
|
845
|
+
let entries;
|
|
846
|
+
try {
|
|
847
|
+
entries = await fs.readdir(contextDir, { withFileTypes: true });
|
|
848
|
+
}
|
|
849
|
+
catch {
|
|
850
|
+
return;
|
|
851
|
+
} // dir doesn't exist yet
|
|
852
|
+
const archPath = path.join(contextDir, 'architecture.md');
|
|
853
|
+
const stray = [];
|
|
854
|
+
for (const e of entries) {
|
|
855
|
+
if (!e.isFile())
|
|
856
|
+
continue;
|
|
857
|
+
if (!e.name.endsWith('.md'))
|
|
858
|
+
continue;
|
|
859
|
+
if (e.name === 'architecture.md' || e.name === 'explorer-last.md')
|
|
860
|
+
continue;
|
|
861
|
+
stray.push(e.name);
|
|
862
|
+
}
|
|
863
|
+
if (stray.length === 0)
|
|
864
|
+
return;
|
|
865
|
+
log.info(`Limpiando ${stray.length} archivo(s) fuera de estructura en .agent/context/`);
|
|
866
|
+
for (const name of stray) {
|
|
867
|
+
const filePath = path.join(contextDir, name);
|
|
868
|
+
// Old timestamped explorer reports — just delete (content is redundant)
|
|
869
|
+
if (/^explorer-\d{4}-\d{2}-/.test(name)) {
|
|
870
|
+
await fs.unlink(filePath);
|
|
871
|
+
log.ok(` Eliminado: ${name}`);
|
|
872
|
+
continue;
|
|
873
|
+
}
|
|
874
|
+
// Other stray files — migrate content to architecture.md, then delete
|
|
875
|
+
try {
|
|
876
|
+
const content = (await readFile(filePath)).trim();
|
|
877
|
+
if (content) {
|
|
878
|
+
let arch = '';
|
|
879
|
+
try {
|
|
880
|
+
arch = await readFile(archPath);
|
|
881
|
+
}
|
|
882
|
+
catch { /* no arch yet */ }
|
|
883
|
+
const label = name.replace(/\.md$/, '');
|
|
884
|
+
await writeFile(archPath, `${arch}\n\n---\n## ${label}\n\n${content}\n`);
|
|
885
|
+
log.ok(` Migrado: ${name} → architecture.md`);
|
|
886
|
+
}
|
|
887
|
+
else {
|
|
888
|
+
log.ok(` Eliminado (vacío): ${name}`);
|
|
889
|
+
}
|
|
890
|
+
await fs.unlink(filePath);
|
|
891
|
+
}
|
|
892
|
+
catch (err) {
|
|
893
|
+
log.warn(` No se pudo limpiar ${name}: ${err.message}`);
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
}
|
|
771
897
|
async runExplorer(task) {
|
|
772
898
|
if (!this.config.roles.explorer) {
|
|
773
899
|
if (!this.config.fallback_global) {
|
|
@@ -782,6 +908,8 @@ INSTRUCCIONES:
|
|
|
782
908
|
const contextDir = path.join(agentDir, 'context');
|
|
783
909
|
await fs.mkdir(contextDir, { recursive: true });
|
|
784
910
|
await fs.mkdir(path.join(agentDir, 'rules'), { recursive: true });
|
|
911
|
+
// Clean up stray files before running
|
|
912
|
+
await this._cleanContextDir(contextDir);
|
|
785
913
|
// Read existing architecture doc if any
|
|
786
914
|
const archPath = path.join(contextDir, 'architecture.md');
|
|
787
915
|
let existingArch = '';
|
|
@@ -809,16 +937,16 @@ INSTRUCCIONES:
|
|
|
809
937
|
7. Al terminar lista todos los archivos creados/actualizados.
|
|
810
938
|
|
|
811
939
|
REGLAS:
|
|
812
|
-
-
|
|
940
|
+
- Escribe UNICAMENTE los archivos indicados: ${archPath} y ${contextDir}/<servicio>/architecture.md
|
|
941
|
+
- NO crees archivos adicionales ni con otros nombres
|
|
942
|
+
- NO modifiques archivos de aplicacion
|
|
813
943
|
- NO ejecutes comandos que cambien estado (npm install, migraciones, etc.)
|
|
814
944
|
- Si un directorio esta en node_modules, dist, .git: ignoralo`;
|
|
815
945
|
const res = await this.runWithFallback('explorer', prompt, 'Exploracion');
|
|
816
946
|
const text = extractCliText(res);
|
|
817
|
-
//
|
|
947
|
+
// Overwrite the single last-run report (no timestamp accumulation)
|
|
818
948
|
try {
|
|
819
|
-
|
|
820
|
-
await writeFile(path.join(contextDir, `explorer-${ts}.md`), `# Explorer Report\n\nTask: ${effectiveTask}\nDate: ${new Date().toISOString()}\n\n${text}\n`);
|
|
821
|
-
log.ok(`Saved to .agent/context/explorer-${ts}.md`);
|
|
949
|
+
await writeFile(path.join(contextDir, 'explorer-last.md'), `# Explorer Report\n\nTask: ${effectiveTask}\nDate: ${new Date().toISOString()}\n\n${text}\n`);
|
|
822
950
|
}
|
|
823
951
|
catch { /* don't fail if save fails */ }
|
|
824
952
|
return text;
|
|
@@ -832,6 +960,8 @@ REGLAS:
|
|
|
832
960
|
const agentDir = path.join(this.projectDir, '.agent');
|
|
833
961
|
const contextDir = path.join(agentDir, 'context');
|
|
834
962
|
await fs.mkdir(contextDir, { recursive: true });
|
|
963
|
+
// Clean up stray files before running
|
|
964
|
+
await this._cleanContextDir(contextDir);
|
|
835
965
|
const archPath = path.join(contextDir, 'architecture.md');
|
|
836
966
|
let existingArch = '';
|
|
837
967
|
try {
|
|
@@ -852,12 +982,19 @@ INSTRUCCIONES:
|
|
|
852
982
|
2. Para cada uno: lee package.json/requirements.txt, explora src/, identifica entry point, rutas/endpoints, puerto.
|
|
853
983
|
3. Identifica dependencias entre servicios.
|
|
854
984
|
4. Crea/actualiza ${archPath} con tabla resumen y detalle por servicio.
|
|
855
|
-
5. Crea/actualiza ${contextDir}/<servicio>/architecture.md para cada servicio
|
|
985
|
+
5. Crea/actualiza ${contextDir}/<servicio>/architecture.md para cada servicio.
|
|
986
|
+
|
|
987
|
+
REGLAS:
|
|
988
|
+
- Escribe UNICAMENTE los archivos indicados: ${archPath} y ${contextDir}/<servicio>/architecture.md
|
|
989
|
+
- NO crees archivos adicionales ni con otros nombres en ningun directorio`);
|
|
856
990
|
let result;
|
|
991
|
+
const sp = this._startSpinner(`agent-explorer ${role.model}`);
|
|
857
992
|
try {
|
|
858
|
-
result = await callQwenAPI(prompt, role.model);
|
|
993
|
+
result = await callQwenAPI(prompt, role.model, (c) => this._parseChunk(c).forEach(l => sp.push(l)));
|
|
994
|
+
sp.stop();
|
|
859
995
|
}
|
|
860
996
|
catch (err) {
|
|
997
|
+
sp.stop();
|
|
861
998
|
if (err.message?.startsWith('QWEN_AUTH_EXPIRED')) {
|
|
862
999
|
console.log(chalk.red('\n ✗ Sesión Qwen expirada.'));
|
|
863
1000
|
console.log(chalk.yellow(' Ejecutá: agent-mp --login (o agent-explorer --login)\n'));
|
|
@@ -866,9 +1003,7 @@ INSTRUCCIONES:
|
|
|
866
1003
|
throw err;
|
|
867
1004
|
}
|
|
868
1005
|
try {
|
|
869
|
-
|
|
870
|
-
await writeFile(path.join(contextDir, `explorer-${ts}.md`), `# Explorer Report\n\nTask: ${effectiveTask}\n\n${result}\n`);
|
|
871
|
-
log.ok(`Saved to .agent/context/explorer-${ts}.md`);
|
|
1006
|
+
await writeFile(path.join(contextDir, 'explorer-last.md'), `# Explorer Report\n\nTask: ${effectiveTask}\nDate: ${new Date().toISOString()}\n\n${result}\n`);
|
|
872
1007
|
}
|
|
873
1008
|
catch { /* ignore */ }
|
|
874
1009
|
return result;
|
package/dist/ui/input.d.ts
CHANGED
|
@@ -3,22 +3,39 @@ export declare class FixedInput {
|
|
|
3
3
|
private history;
|
|
4
4
|
private histIdx;
|
|
5
5
|
private origLog;
|
|
6
|
+
private _pasting;
|
|
7
|
+
private _pasteAccum;
|
|
8
|
+
private _drawPending;
|
|
9
|
+
private _activityHeader;
|
|
10
|
+
private _activityLines;
|
|
6
11
|
private get rows();
|
|
7
12
|
get cols(): number;
|
|
13
|
+
private get _reservedRows();
|
|
8
14
|
private get scrollBottom();
|
|
9
15
|
private _contentRows;
|
|
10
16
|
setup(): void;
|
|
11
17
|
teardown(): void;
|
|
12
18
|
redrawBox(): void;
|
|
13
19
|
suspend(): () => void;
|
|
20
|
+
/** Enter activity mode: show the 5-line log box instead of the input box. */
|
|
21
|
+
startActivity(header: string): void;
|
|
22
|
+
/** Update the header line (spinner frame + elapsed time) without clearing lines. */
|
|
23
|
+
updateActivityHeader(header: string): void;
|
|
24
|
+
/**
|
|
25
|
+
* Append a line to the activity log (keeps last ACTIVITY_LINES lines).
|
|
26
|
+
* Strips ANSI codes and skips blank or pure-JSON lines.
|
|
27
|
+
*/
|
|
28
|
+
pushActivity(rawLine: string): void;
|
|
29
|
+
/** Leave activity mode and restore the normal input box. */
|
|
30
|
+
stopActivity(): void;
|
|
14
31
|
readLine(): Promise<string>;
|
|
15
32
|
println(text: string): void;
|
|
16
33
|
printSeparator(): void;
|
|
17
|
-
|
|
34
|
+
private _scheduleDraw;
|
|
18
35
|
private _setScrollRegion;
|
|
19
|
-
/** Blank every row in the reserved area. */
|
|
20
36
|
private _clearReserved;
|
|
21
37
|
private _drawBox;
|
|
22
|
-
|
|
38
|
+
private _drawActivityBox;
|
|
39
|
+
private _drawInputBox;
|
|
23
40
|
private _wrapText;
|
|
24
41
|
}
|
package/dist/ui/input.js
CHANGED
|
@@ -5,27 +5,35 @@ const B = (s) => chalk.rgb(30, 110, 185)(s); // blue — prompt arrow
|
|
|
5
5
|
const PREFIX = T('│') + B(' > ');
|
|
6
6
|
const PREFIX_CONT = T('│') + B(' '); // continuation lines
|
|
7
7
|
const PREFIX_COLS = 4; // visual width of "│ > " and "│ "
|
|
8
|
-
// Maximum content rows the box can grow to (Shift+Enter / word-wrap).
|
|
9
|
-
// The reserved area at the bottom is MAX_CONTENT_ROWS + 2 (borders).
|
|
8
|
+
// Maximum content rows the input box can grow to (Shift+Enter / word-wrap).
|
|
10
9
|
const MAX_CONTENT_ROWS = 4;
|
|
11
|
-
const
|
|
10
|
+
const ACTIVITY_LINES = 5;
|
|
11
|
+
// Reserved rows at the bottom:
|
|
12
|
+
// Idle: 7 = 1 status row + up to 4 content + 2 borders
|
|
13
|
+
// Active: 10 = 7 (activity box) + 3 (input box: 1 content + 2 borders)
|
|
14
|
+
// The scroll region is updated whenever activity mode toggles.
|
|
15
|
+
const IDLE_RESERVED = MAX_CONTENT_ROWS + 3; // 7
|
|
16
|
+
const ACTIVE_RESERVED = ACTIVITY_LINES + 2 + 3; // 10 = activity(7) + input(3)
|
|
12
17
|
// ─── FixedInput ──────────────────────────────────────────────────────────────
|
|
13
|
-
// Keeps an input box pinned to the physical bottom of the terminal.
|
|
14
|
-
// The box starts as 3 rows (border + 1 content + border) and grows up to
|
|
15
|
-
// RESERVED_ROWS when the user types multiline text (Shift+Enter) or the
|
|
16
|
-
// text wraps. The scroll region is set ONCE at setup (and on resize) to
|
|
17
|
-
// [1 .. rows-RESERVED_ROWS] so DECSTBM never fires during normal typing.
|
|
18
18
|
export class FixedInput {
|
|
19
19
|
buf = '';
|
|
20
20
|
history = [];
|
|
21
21
|
histIdx = -1;
|
|
22
22
|
origLog;
|
|
23
|
+
_pasting = false;
|
|
24
|
+
_pasteAccum = '';
|
|
25
|
+
_drawPending = false;
|
|
26
|
+
// ── Activity box state (null = input mode, string = activity mode) ──────────
|
|
27
|
+
_activityHeader = null;
|
|
28
|
+
_activityLines = [];
|
|
23
29
|
get rows() { return process.stdout.rows || 24; }
|
|
24
30
|
get cols() { return process.stdout.columns || 80; }
|
|
25
|
-
|
|
26
|
-
get scrollBottom() { return this.rows -
|
|
27
|
-
// How many content rows the current buffer needs (1 .. MAX_CONTENT_ROWS).
|
|
31
|
+
get _reservedRows() { return this._activityHeader !== null ? ACTIVE_RESERVED : IDLE_RESERVED; }
|
|
32
|
+
get scrollBottom() { return this.rows - this._reservedRows; }
|
|
28
33
|
_contentRows() {
|
|
34
|
+
// During activity mode only 1 content row fits below the activity box
|
|
35
|
+
if (this._activityHeader !== null)
|
|
36
|
+
return 1;
|
|
29
37
|
const w = this.cols - PREFIX_COLS - 2;
|
|
30
38
|
if (w <= 0)
|
|
31
39
|
return 1;
|
|
@@ -47,6 +55,7 @@ export class FixedInput {
|
|
|
47
55
|
process.stdout.write(`\x1b[${this.scrollBottom};1H`);
|
|
48
56
|
this._clearReserved();
|
|
49
57
|
this._drawBox();
|
|
58
|
+
process.stdout.write('\x1b[?2004h');
|
|
50
59
|
process.stdout.on('resize', () => {
|
|
51
60
|
this._setScrollRegion();
|
|
52
61
|
this._clearReserved();
|
|
@@ -54,14 +63,18 @@ export class FixedInput {
|
|
|
54
63
|
});
|
|
55
64
|
}
|
|
56
65
|
teardown() {
|
|
66
|
+
this._activityHeader = null;
|
|
67
|
+
this._activityLines = [];
|
|
57
68
|
console.log = this.origLog;
|
|
58
|
-
process.stdout.write('\x1b[
|
|
59
|
-
process.stdout.write('\x1b[
|
|
69
|
+
process.stdout.write('\x1b[?2004l');
|
|
70
|
+
process.stdout.write('\x1b[r');
|
|
71
|
+
process.stdout.write('\x1b[?25h');
|
|
60
72
|
process.stdout.write(`\x1b[${this.rows};1H\n`);
|
|
61
73
|
}
|
|
62
74
|
redrawBox() { this._drawBox(); }
|
|
63
75
|
suspend() {
|
|
64
76
|
console.log = this.origLog;
|
|
77
|
+
process.stdout.write('\x1b[?2004l');
|
|
65
78
|
process.stdout.write('\x1b[r');
|
|
66
79
|
this._clearReserved();
|
|
67
80
|
process.stdout.write(`\x1b[${this.scrollBottom};1H`);
|
|
@@ -73,8 +86,48 @@ export class FixedInput {
|
|
|
73
86
|
this._setScrollRegion();
|
|
74
87
|
this._clearReserved();
|
|
75
88
|
this._drawBox();
|
|
89
|
+
process.stdout.write('\x1b[?2004h');
|
|
76
90
|
};
|
|
77
91
|
}
|
|
92
|
+
// ── Activity box API ───────────────────────────────────────────────────────
|
|
93
|
+
/** Enter activity mode: show the 5-line log box instead of the input box. */
|
|
94
|
+
startActivity(header) {
|
|
95
|
+
this._activityHeader = header;
|
|
96
|
+
this._activityLines = [];
|
|
97
|
+
this._setScrollRegion();
|
|
98
|
+
this._drawBox();
|
|
99
|
+
}
|
|
100
|
+
/** Update the header line (spinner frame + elapsed time) without clearing lines. */
|
|
101
|
+
updateActivityHeader(header) {
|
|
102
|
+
this._activityHeader = header;
|
|
103
|
+
this._drawBox();
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Append a line to the activity log (keeps last ACTIVITY_LINES lines).
|
|
107
|
+
* Strips ANSI codes and skips blank or pure-JSON lines.
|
|
108
|
+
*/
|
|
109
|
+
pushActivity(rawLine) {
|
|
110
|
+
if (this._activityHeader === null)
|
|
111
|
+
return;
|
|
112
|
+
// Strip ANSI escape sequences
|
|
113
|
+
const clean = rawLine
|
|
114
|
+
.replace(/\x1b\[[0-9;]*[A-Za-z]/g, '')
|
|
115
|
+
.replace(/[^\x20-\x7e\u00a0-\uffff]/g, '')
|
|
116
|
+
.trim();
|
|
117
|
+
if (!clean)
|
|
118
|
+
return;
|
|
119
|
+
this._activityLines.push(clean);
|
|
120
|
+
if (this._activityLines.length > ACTIVITY_LINES)
|
|
121
|
+
this._activityLines.shift();
|
|
122
|
+
this._scheduleDraw();
|
|
123
|
+
}
|
|
124
|
+
/** Leave activity mode and restore the normal input box. */
|
|
125
|
+
stopActivity() {
|
|
126
|
+
this._activityHeader = null;
|
|
127
|
+
this._activityLines = [];
|
|
128
|
+
this._setScrollRegion();
|
|
129
|
+
this._drawBox();
|
|
130
|
+
}
|
|
78
131
|
// ── Input ──────────────────────────────────────────────────────────────────
|
|
79
132
|
readLine() {
|
|
80
133
|
this.buf = '';
|
|
@@ -94,17 +147,39 @@ export class FixedInput {
|
|
|
94
147
|
const onData = (data) => {
|
|
95
148
|
const hex = data.toString('hex');
|
|
96
149
|
const key = data.toString();
|
|
97
|
-
// ──
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
150
|
+
// ── Bracketed paste: start ────────────────────────────────────
|
|
151
|
+
if (key.includes('\x1b[200~')) {
|
|
152
|
+
this._pasting = true;
|
|
153
|
+
this._pasteAccum = '';
|
|
154
|
+
const after = key.slice(key.indexOf('\x1b[200~') + 6);
|
|
155
|
+
if (after)
|
|
156
|
+
this._pasteAccum += after;
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
// ── Bracketed paste: accumulate ───────────────────────────────
|
|
160
|
+
if (this._pasting) {
|
|
161
|
+
if (key.includes('\x1b[201~')) {
|
|
162
|
+
const before = key.slice(0, key.indexOf('\x1b[201~'));
|
|
163
|
+
this._pasteAccum += before;
|
|
164
|
+
this.buf += this._pasteAccum;
|
|
165
|
+
this._pasting = false;
|
|
166
|
+
this._pasteAccum = '';
|
|
167
|
+
this._scheduleDraw();
|
|
168
|
+
}
|
|
169
|
+
else {
|
|
170
|
+
this._pasteAccum += key;
|
|
171
|
+
}
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
// ── Shift+Enter → newline ────────────────────────────────────
|
|
175
|
+
if (hex === '5c0d' ||
|
|
176
|
+
key === '\x0a' ||
|
|
177
|
+
hex === '1b5b31333b327e' ||
|
|
178
|
+
hex === '1b5b31333b3275' ||
|
|
179
|
+
hex === '1b4f4d') {
|
|
105
180
|
this.buf += '\n';
|
|
106
|
-
this.
|
|
107
|
-
// ── Enter → submit
|
|
181
|
+
this._scheduleDraw();
|
|
182
|
+
// ── Enter → submit ───────────────────────────────────────────
|
|
108
183
|
}
|
|
109
184
|
else if (key === '\r') {
|
|
110
185
|
const line = this.buf;
|
|
@@ -115,28 +190,28 @@ export class FixedInput {
|
|
|
115
190
|
}
|
|
116
191
|
done(line);
|
|
117
192
|
}
|
|
118
|
-
else if (key === '\x7f' || key === '\x08') {
|
|
193
|
+
else if (key === '\x7f' || key === '\x08') {
|
|
119
194
|
if (this.buf.length > 0) {
|
|
120
195
|
this.buf = this.buf.slice(0, -1);
|
|
121
|
-
this.
|
|
196
|
+
this._scheduleDraw();
|
|
122
197
|
}
|
|
123
198
|
}
|
|
124
|
-
else if (key === '\x03') {
|
|
199
|
+
else if (key === '\x03') {
|
|
125
200
|
this.teardown();
|
|
126
201
|
process.exit(0);
|
|
127
202
|
}
|
|
128
|
-
else if (key === '\x04') {
|
|
203
|
+
else if (key === '\x04') {
|
|
129
204
|
done('/exit');
|
|
130
205
|
}
|
|
131
|
-
else if (key === '\x15') {
|
|
206
|
+
else if (key === '\x15') {
|
|
132
207
|
this.buf = '';
|
|
133
|
-
this.
|
|
208
|
+
this._scheduleDraw();
|
|
134
209
|
}
|
|
135
210
|
else if (hex === '1b5b41') { // Arrow ↑
|
|
136
211
|
if (this.histIdx + 1 < this.history.length) {
|
|
137
212
|
this.histIdx++;
|
|
138
213
|
this.buf = this.history[this.histIdx];
|
|
139
|
-
this.
|
|
214
|
+
this._scheduleDraw();
|
|
140
215
|
}
|
|
141
216
|
}
|
|
142
217
|
else if (hex === '1b5b42') { // Arrow ↓
|
|
@@ -148,11 +223,11 @@ export class FixedInput {
|
|
|
148
223
|
this.histIdx = -1;
|
|
149
224
|
this.buf = '';
|
|
150
225
|
}
|
|
151
|
-
this.
|
|
226
|
+
this._scheduleDraw();
|
|
152
227
|
}
|
|
153
228
|
else if (key.length >= 1 && key.charCodeAt(0) >= 32 && !key.startsWith('\x1b')) {
|
|
154
229
|
this.buf += key;
|
|
155
|
-
this.
|
|
230
|
+
this._scheduleDraw();
|
|
156
231
|
}
|
|
157
232
|
};
|
|
158
233
|
process.stdin.on('data', onData);
|
|
@@ -168,30 +243,58 @@ export class FixedInput {
|
|
|
168
243
|
this.println(chalk.rgb(0, 120, 116)('─'.repeat(this.cols - 1)));
|
|
169
244
|
}
|
|
170
245
|
// ── Private drawing ────────────────────────────────────────────────────────
|
|
171
|
-
|
|
246
|
+
_scheduleDraw() {
|
|
247
|
+
if (this._drawPending)
|
|
248
|
+
return;
|
|
249
|
+
this._drawPending = true;
|
|
250
|
+
setImmediate(() => { this._drawPending = false; this._drawBox(); });
|
|
251
|
+
}
|
|
172
252
|
_setScrollRegion() {
|
|
173
253
|
const sb = this.scrollBottom;
|
|
174
254
|
if (sb >= 1)
|
|
175
255
|
process.stdout.write(`\x1b[1;${sb}r`);
|
|
176
256
|
}
|
|
177
|
-
/** Blank every row in the reserved area. */
|
|
178
257
|
_clearReserved() {
|
|
179
258
|
for (let r = this.scrollBottom + 1; r <= this.rows; r++)
|
|
180
259
|
process.stdout.write(`\x1b[${r};1H\x1b[2K`);
|
|
181
260
|
}
|
|
182
261
|
_drawBox() {
|
|
262
|
+
process.stdout.write('\x1b[?25l');
|
|
263
|
+
this._clearReserved();
|
|
264
|
+
if (this._activityHeader !== null) {
|
|
265
|
+
this._drawActivityBox();
|
|
266
|
+
}
|
|
267
|
+
this._drawInputBox();
|
|
268
|
+
process.stdout.write('\x1b[?25h');
|
|
269
|
+
}
|
|
270
|
+
// ── Activity box (shown while a subagent is running) ───────────────────────
|
|
271
|
+
_drawActivityBox() {
|
|
272
|
+
const cols = this.cols;
|
|
273
|
+
const inner = cols - 4; // │ + space + content + space + │
|
|
274
|
+
const topRow = this.scrollBottom + 1;
|
|
275
|
+
const header = (this._activityHeader || '').slice(0, cols - 4);
|
|
276
|
+
const dashFill = Math.max(0, cols - 3 - header.length);
|
|
277
|
+
// Top border with header text
|
|
278
|
+
process.stdout.write(`\x1b[${topRow};1H`);
|
|
279
|
+
process.stdout.write(T('╭─') + chalk.bold.white(header) + T('─'.repeat(dashFill)) + T('╮'));
|
|
280
|
+
// Content rows (last ACTIVITY_LINES lines, or blank)
|
|
281
|
+
for (let i = 0; i < ACTIVITY_LINES; i++) {
|
|
282
|
+
const row = topRow + 1 + i;
|
|
283
|
+
const line = (this._activityLines[i] ?? '').slice(0, inner);
|
|
284
|
+
const pad = inner - line.length;
|
|
285
|
+
process.stdout.write(`\x1b[${row};1H`);
|
|
286
|
+
process.stdout.write(T('│') + ' ' + chalk.rgb(180, 210, 210)(line) + ' '.repeat(pad) + ' ' + T('│'));
|
|
287
|
+
}
|
|
288
|
+
// Bottom border
|
|
289
|
+
process.stdout.write(`\x1b[${topRow + ACTIVITY_LINES + 1};1H`);
|
|
290
|
+
process.stdout.write(T('╰') + T('─'.repeat(cols - 2)) + T('╯'));
|
|
291
|
+
}
|
|
292
|
+
// ── Normal input box ───────────────────────────────────────────────────────
|
|
293
|
+
_drawInputBox() {
|
|
183
294
|
const cols = this.cols;
|
|
184
295
|
const cRows = this._contentRows();
|
|
185
296
|
const cWidth = cols - PREFIX_COLS - 2;
|
|
186
|
-
// The box occupies the bottom of the terminal:
|
|
187
|
-
// topBorder = rows - cRows - 1
|
|
188
|
-
// content rows = rows - cRows ... rows - 1
|
|
189
|
-
// bottomBorder = rows
|
|
190
297
|
const topBorder = this.rows - cRows - 1;
|
|
191
|
-
// Hide cursor while repainting
|
|
192
|
-
process.stdout.write('\x1b[?25l');
|
|
193
|
-
// Clear entire reserved area (removes stale content from previous draws)
|
|
194
|
-
this._clearReserved();
|
|
195
298
|
// ── Top border ───────────────────────────────────────────────
|
|
196
299
|
process.stdout.write(`\x1b[${topBorder};1H`);
|
|
197
300
|
process.stdout.write(T('╭') + T('─'.repeat(cols - 2)));
|
|
@@ -203,7 +306,6 @@ export class FixedInput {
|
|
|
203
306
|
for (let i = 0; i < cRows; i++) {
|
|
204
307
|
const row = topBorder + 1 + i;
|
|
205
308
|
let line = visible[i] ?? '';
|
|
206
|
-
// Show overflow indicator when content is clipped above
|
|
207
309
|
if (i === 0 && showStart > 0)
|
|
208
310
|
line = '… ' + line.slice(0, Math.max(0, cWidth - 2));
|
|
209
311
|
else
|
|
@@ -217,18 +319,14 @@ export class FixedInput {
|
|
|
217
319
|
process.stdout.write(`\x1b[${this.rows};1H`);
|
|
218
320
|
process.stdout.write(T('╰') + T('─'.repeat(cols - 2)));
|
|
219
321
|
process.stdout.write(`\x1b[${cols}G` + T('╯'));
|
|
220
|
-
// ── Position cursor
|
|
322
|
+
// ── Position cursor ──────────────────────────────────────────
|
|
221
323
|
const lastLine = visible[visible.length - 1] ?? '';
|
|
222
|
-
const cursorRow = topBorder + cRows;
|
|
324
|
+
const cursorRow = topBorder + cRows;
|
|
223
325
|
const cursorCol = PREFIX_COLS + 1 + lastLine.length;
|
|
224
326
|
process.stdout.write(`\x1b[${cursorRow};${cursorCol}H`);
|
|
225
|
-
process.stdout.write('\x1b[?25h');
|
|
226
327
|
}
|
|
227
|
-
/** Split text into visual lines: split on \n, then wrap each segment. */
|
|
228
328
|
_wrapText(text, maxWidth) {
|
|
229
|
-
if (!text)
|
|
230
|
-
return [''];
|
|
231
|
-
if (maxWidth <= 0)
|
|
329
|
+
if (!text || maxWidth <= 0)
|
|
232
330
|
return [''];
|
|
233
331
|
const result = [];
|
|
234
332
|
for (const seg of text.split('\n')) {
|
|
@@ -15,7 +15,7 @@ export declare function getQwenAccessToken(): Promise<string | null>;
|
|
|
15
15
|
* The qwen CLI manages its own token refresh and uses the correct API format.
|
|
16
16
|
* Falls back to direct HTTP call if the qwen CLI is not available.
|
|
17
17
|
*/
|
|
18
|
-
export declare function callQwenAPI(prompt: string, model?: string): Promise<string>;
|
|
18
|
+
export declare function callQwenAPI(prompt: string, model?: string, onData?: (chunk: string) => void): Promise<string>;
|
|
19
19
|
/**
|
|
20
20
|
* Call Qwen API using credentials from a specific file path (for role binaries).
|
|
21
21
|
* The role binary CLI (e.g. agent-explorer) manages its own qwen auth via the
|
|
@@ -23,4 +23,4 @@ export declare function callQwenAPI(prompt: string, model?: string): Promise<str
|
|
|
23
23
|
* in non-interactive mode without TTY issues.
|
|
24
24
|
* Falls back to direct HTTP if the role binary is not found.
|
|
25
25
|
*/
|
|
26
|
-
export declare function callQwenAPIFromCreds(prompt: string, model: string, credsPath: string): Promise<string>;
|
|
26
|
+
export declare function callQwenAPIFromCreds(prompt: string, model: string, credsPath: string, onData?: (chunk: string) => void): Promise<string>;
|
package/dist/utils/qwen-auth.js
CHANGED
|
@@ -1,7 +1,25 @@
|
|
|
1
1
|
import * as fs from 'fs/promises';
|
|
2
2
|
import * as path from 'path';
|
|
3
3
|
import * as crypto from 'crypto';
|
|
4
|
-
import {
|
|
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
|
+
}
|
|
5
23
|
import open from 'open';
|
|
6
24
|
import { AGENT_HOME } from './config.js';
|
|
7
25
|
const QWEN_OAUTH_BASE_URL = 'https://chat.qwen.ai';
|
|
@@ -279,17 +297,12 @@ async function callQwenAPIWithToken(token, prompt, model) {
|
|
|
279
297
|
* The qwen CLI manages its own token refresh and uses the correct API format.
|
|
280
298
|
* Falls back to direct HTTP call if the qwen CLI is not available.
|
|
281
299
|
*/
|
|
282
|
-
export async function callQwenAPI(prompt, model = 'coder-model') {
|
|
300
|
+
export async function callQwenAPI(prompt, model = 'coder-model', onData) {
|
|
283
301
|
// Try using the qwen CLI subprocess first — it handles auth/refresh/format automatically
|
|
284
302
|
const qwenBin = process.env.QWEN_BIN || 'qwen';
|
|
285
303
|
try {
|
|
286
|
-
const result =
|
|
287
|
-
|
|
288
|
-
encoding: 'utf-8',
|
|
289
|
-
timeout: 300000, // 5 minutes
|
|
290
|
-
maxBuffer: 10 * 1024 * 1024,
|
|
291
|
-
});
|
|
292
|
-
if (result.status === 0 && result.stdout?.trim()) {
|
|
304
|
+
const result = await spawnAsync(qwenBin, [], prompt, 300000, onData);
|
|
305
|
+
if (result.status === 0 && result.stdout.trim()) {
|
|
293
306
|
return result.stdout.trim();
|
|
294
307
|
}
|
|
295
308
|
// qwen not available or failed — fall through to direct API
|
|
@@ -325,19 +338,14 @@ export async function callQwenAPI(prompt, model = 'coder-model') {
|
|
|
325
338
|
* in non-interactive mode without TTY issues.
|
|
326
339
|
* Falls back to direct HTTP if the role binary is not found.
|
|
327
340
|
*/
|
|
328
|
-
export async function callQwenAPIFromCreds(prompt, model, credsPath) {
|
|
341
|
+
export async function callQwenAPIFromCreds(prompt, model, credsPath, onData) {
|
|
329
342
|
// Derive the role binary name from the creds path (e.g. ~/.agent-explorer/ → agent-explorer)
|
|
330
343
|
const cliName = path.basename(path.dirname(credsPath)).replace(/^\./, '');
|
|
331
|
-
// Try spawning the
|
|
344
|
+
// Try spawning the qwen CLI with piped stdin (async — keeps event loop free)
|
|
332
345
|
const qwenBin = process.env.QWEN_BIN || 'qwen';
|
|
333
346
|
try {
|
|
334
|
-
const result =
|
|
335
|
-
|
|
336
|
-
encoding: 'utf-8',
|
|
337
|
-
timeout: 300000,
|
|
338
|
-
maxBuffer: 10 * 1024 * 1024,
|
|
339
|
-
});
|
|
340
|
-
if (result.status === 0 && result.stdout?.trim()) {
|
|
347
|
+
const result = await spawnAsync(qwenBin, [], prompt, 300000, onData);
|
|
348
|
+
if (result.status === 0 && result.stdout.trim()) {
|
|
341
349
|
return result.stdout.trim();
|
|
342
350
|
}
|
|
343
351
|
}
|
package/package.json
CHANGED
|
@@ -1 +1,44 @@
|
|
|
1
|
-
{
|
|
1
|
+
{
|
|
2
|
+
"name": "agent-rev",
|
|
3
|
+
"version": "0.4.3",
|
|
4
|
+
"description": "Deterministic multi-agent CLI orchestrator \u2014 plan, code, review",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"files": [
|
|
8
|
+
"dist/"
|
|
9
|
+
],
|
|
10
|
+
"bin": {
|
|
11
|
+
"agent-mp": "dist/index.js"
|
|
12
|
+
},
|
|
13
|
+
"scripts": {
|
|
14
|
+
"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",
|
|
15
|
+
"dev": "tsx src/index.ts",
|
|
16
|
+
"prepublishOnly": "npm run build"
|
|
17
|
+
},
|
|
18
|
+
"keywords": [
|
|
19
|
+
"ai",
|
|
20
|
+
"agent",
|
|
21
|
+
"orchestrator",
|
|
22
|
+
"multi-agent",
|
|
23
|
+
"cli",
|
|
24
|
+
"coding"
|
|
25
|
+
],
|
|
26
|
+
"license": "MIT",
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"@anthropic-ai/sdk": "^0.39.0",
|
|
29
|
+
"@google/generative-ai": "^0.24.0",
|
|
30
|
+
"chalk": "^5.4.1",
|
|
31
|
+
"commander": "^13.1.0",
|
|
32
|
+
"open": "^11.0.0",
|
|
33
|
+
"openai": "^4.91.0"
|
|
34
|
+
},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"@types/node": "^22.13.0",
|
|
37
|
+
"@types/open": "^6.1.0",
|
|
38
|
+
"tsx": "^4.19.3",
|
|
39
|
+
"typescript": "^5.7.3"
|
|
40
|
+
},
|
|
41
|
+
"engines": {
|
|
42
|
+
"node": ">=18.0.0"
|
|
43
|
+
}
|
|
44
|
+
}
|