@autonsh/cli 0.16.5 → 0.17.2

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/index.js CHANGED
@@ -15,7 +15,11 @@ import { SingleBar, Presets } from "cli-progress";
15
15
  import pc from "picocolors";
16
16
  import { ok, fail, info, dim, bold, url, spinner, logo, tagLine, statusColor, table, box, isTTY, } from "./ui.js";
17
17
  import { cmdLogin as loginCmd, cmdLogout as logoutCmd, credentialEnv } from "./login.js";
18
- import { TEMPLATES, promptInit, promptConfirmUndeploy, promptGateApproval, } from "./prompts.js";
18
+ import { cmdModel, modelLs, modelSet, modelTest } from "./model.js";
19
+ import { cmdChat } from "./chat.js";
20
+ import { resolveAgent } from "./picker.js";
21
+ import { getDefaultModel, getActiveProvider } from "./config.js";
22
+ import { TEMPLATES, promptInit, promptConfirmUndeploy, promptGateApproval, promptGateQuestion, parseGateLine, } from "./prompts.js";
19
23
  import { loadTaskManifest, MODES, SKILLS, EXIT, ExitError, } from "./task.js";
20
24
  import { runVerify } from "./verify.js";
21
25
  const SRC = dirname(fileURLToPath(import.meta.url));
@@ -45,8 +49,8 @@ function ensureRuntimeLink(dir) {
45
49
  * TTY: barra de progresso com fases do npm. Pipe: silencioso, 1 linha. */
46
50
  async function installRuntime(dir) {
47
51
  const run = (onLine) => new Promise((resolveP, reject) => {
48
- const args = ["install", "@autonsh/runtime", "--no-audit", "--no-fund", "--loglevel=info"];
49
- const child = spawn("npm", args, {
52
+ const realArgs = ["install", "@autonsh/runtime", "--no-audit", "--no-fund", "--loglevel=info"];
53
+ const child = spawn("npm", realArgs, {
50
54
  cwd: dir,
51
55
  stdio: ["ignore", "ignore", "pipe"],
52
56
  shell: process.platform === "win32",
@@ -157,10 +161,79 @@ async function cmdInit(name, template, opts = {}) {
157
161
  if (isTTY)
158
162
  console.log(logo());
159
163
  console.log(ok(`${plain} (template: ${template}) em ${dim(dir)}`));
160
- console.log(info(`próximo: ${bold(`auton run "${plain}" <objetivo>`)}`));
164
+ console.log(info(`próximo: ${bold(`cd ${plain} && auton run "<objetivo>"`)}`));
161
165
  }
162
166
  const VERSION = JSON.parse(readFileSync(resolve(SRC, "../package.json"), "utf8")).version;
163
167
  const API = process.env.AUTON_API ?? "http://localhost:8080";
168
+ let cpStarted = false;
169
+ /** G3: auto-start control plane se não estiver rodando (single-command UX). */
170
+ async function ensureCPRunning() {
171
+ if (cpStarted)
172
+ return;
173
+ try {
174
+ const res = await fetch(`${API}/v1/agents`, {
175
+ method: "GET",
176
+ headers: authHeaders(),
177
+ signal: AbortSignal.timeout(1000),
178
+ });
179
+ if (res.ok) {
180
+ cpStarted = true;
181
+ return;
182
+ }
183
+ }
184
+ catch {
185
+ // CP não responde — tenta iniciar
186
+ }
187
+ // Tenta achar binário do control-plane
188
+ const candidates = [
189
+ // monorepo dev
190
+ resolve(SRC, "../../control-plane/bin/control-plane"),
191
+ // npm install global (se publicado)
192
+ resolve(process.execPath, "../../lib/node_modules/@autonsh/server/bin/control-plane"),
193
+ // fallback PATH
194
+ "control-plane",
195
+ ];
196
+ let cpBin = null;
197
+ for (const c of candidates) {
198
+ try {
199
+ await import("node:fs/promises").then((fs) => fs.access(c, fs.constants.X_OK));
200
+ cpBin = c;
201
+ break;
202
+ }
203
+ catch {
204
+ // não existe ou não executável
205
+ }
206
+ }
207
+ if (!cpBin) {
208
+ console.log(dim("aviso: control-plane não encontrado — assumindo externo"));
209
+ return;
210
+ }
211
+ console.log(info(`iniciando control-plane (${cpBin})...`));
212
+ const child = spawn(cpBin, {
213
+ detached: true,
214
+ stdio: ["ignore", "ignore", "ignore"],
215
+ });
216
+ child.unref();
217
+ // Aguarda CP subir (max 5s)
218
+ for (let i = 0; i < 50; i++) {
219
+ await new Promise((r) => setTimeout(r, 100));
220
+ try {
221
+ const res = await fetch(`${API}/v1/agents`, {
222
+ headers: authHeaders(),
223
+ signal: AbortSignal.timeout(500),
224
+ });
225
+ if (res.ok) {
226
+ cpStarted = true;
227
+ console.log(ok("control-plane iniciado"));
228
+ return;
229
+ }
230
+ }
231
+ catch {
232
+ // ainda subindo
233
+ }
234
+ }
235
+ console.log(dim("aviso: control-plane pode não ter subido a tempo"));
236
+ }
164
237
  function authHeaders(extra) {
165
238
  const h = { ...extra };
166
239
  const token = process.env.AUTON_TOKEN;
@@ -168,37 +241,74 @@ function authHeaders(extra) {
168
241
  h.Authorization = `Bearer ${token}`;
169
242
  return h;
170
243
  }
244
+ /** Nomes dos agentes deployados (0.17.0 — `auton <nome>` abre sessão). */
245
+ async function listAgentNames() {
246
+ try {
247
+ const res = await fetch(`${API}/v1/agents`, { headers: authHeaders() });
248
+ if (!res.ok)
249
+ return [];
250
+ return (await res.json()).map((a) => a.name);
251
+ }
252
+ catch {
253
+ return [];
254
+ }
255
+ }
171
256
  const USAGE = `
172
257
  auton — Tasks that start themselves
173
258
 
174
259
  Usage:
175
- auton run [goal] Roda a task do diretório atual com o objetivo
176
- (usa task.yaml: goal default; gates de aprovação
177
- perguntam em linha — TTY; verify: roda antes — use
178
- --skip-verify pra pular)
179
- auton verify [dir] [--json] Roda gates mecânicos do verify: (task.yaml)
180
- auton inspect [dir] [--json] Inspeciona task + evidência (goal/mode/verify/traces)
181
- auton continue [name] [goal] Retoma a última task (sem redeploy)
182
- auton init [nome] [template] Scaffold de task (support|with-approval|cron-digester|webhook-echo)
183
- auton ps Lista tasks/agentes + status
184
- auton logs [name] Stream de logs da task (SSE)
185
- auton status [name] Status da task
260
+ auton <comando> [argumentos]
261
+
262
+ Modelos:
263
+ auton model Seletor interativo do modelo default
264
+ (local ollama + providers configurados)
265
+ auton model ls [--json] Lista modelos locais + providers
266
+ auton model set <spec> Define default (ex: ollama:qwen3)
267
+ auton model test Testa latência dos providers
268
+ auton login [provider] [--apikey X]
269
+ Conecta provider (gemini/groq/ollama)
270
+ auton logout [id] Remove provider configurado
271
+
272
+ Agentes:
273
+ auton [nome] Sessão de trabalho (estilo opencode): dá
274
+ tasks, acompanha o agente ao vivo, gates
275
+ inline. Sem nome: cwd/picker.
276
+ auton chat [name] Alias da sessão de trabalho
277
+ auton agent [dir] Deploy do agente no diretório
278
+ auton ps Lista agentes + status
279
+ auton logs [name] [--tail N] [--follow] [--since X] [--turn N]
280
+ Stream de logs do agente (SSE)
281
+ auton status [name] Status do agente
282
+ auton restart [name] Reinicia o agente
186
283
  auton invoke [name] [--stream] [-- prompt]
187
- Roda 1 turno (legado v0)
188
- auton kv get|set|ls [name] [key] [value]
189
- Inspeciona o storage KV
190
- auton context [name] Lista work context: decisions/findings/artifacts
284
+ Roda 1 turno (legado v0)
191
285
  auton approve|reject [name] [-- razão]
192
- Decide o gate de aprovação (approval: true)
193
- auton undeploy [name] Remove a task
194
- auton login TODO: auth com control plane (dpk_...)
286
+ Decide o gate de aprovação
287
+ auton answer [name] [-- texto] Responde pergunta pendente (ctx.ask)
288
+ auton context [name] Work context: decisions/findings/artifacts
289
+ auton undeploy [name] Remove o agente (--all remove tudo)
290
+
291
+ Tasks:
292
+ auton run [goal] [--model X] [--skip-verify] [--async]
293
+ Roda a task do diretório atual (--async = fire-and-forget)
294
+ auton wait <task-id> Aguarda task async terminar (poll logs)
295
+ auton result <task-id> Busca resultado final de task async
296
+ auton verify [dir] [--json] Roda gates mecânicos do verify
297
+ auton inspect [dir] [--json] Inspeciona task + evidência
298
+ auton continue [name] [goal] Retoma a última task (sem redeploy)
299
+ auton init [nome] [template] Scaffold de task (support|with-approval|...)
300
+
301
+ Infra:
302
+ auton kv get|set|ls [name] [key] [value]
303
+ Inspeciona o storage KV
304
+ auton env set|get|ls [name] [key] [value]
305
+ Env vars por agente
195
306
 
196
307
  Options:
197
308
  --help, -h Mostra ajuda
198
- --version, -v Mostra versão
309
+ --version, -v Mostra versão + modelo default
199
310
  AUTON_API=http://... Base do control plane (default localhost:8080)
200
-
201
- Nota: commandos legado v0 (deploy agent . / deploy invoke) continuam funcionando.
311
+ --no-auto-cp Desliga auto-start do control plane
202
312
 
203
313
  Exit codes (0.14):
204
314
  0 ok · 1 erro genérico · 2 uso inválido · 3 verificação falhou
@@ -414,24 +524,190 @@ async function cmdRun(dir, goalArg, skipVerify, model) {
414
524
  const manifest = await loadTaskManifest(dir);
415
525
  if (!skipVerify)
416
526
  await verifyPreFlight(dir, manifest);
417
- const goal = goalArg ?? manifest.goal;
527
+ let goal = goalArg ?? manifest.goal;
418
528
  if (!goal) {
419
- throw new Error(`task ${manifest.name} sem objetivo — passe "${bold('auton run "<objetivo>"')}" ou defina 'goal' no task.yaml`);
529
+ if (isTTY) {
530
+ // 0.17: run sem goal pergunta (estilo `ollama run` sem modelo)
531
+ const { text, isCancel } = await import("@clack/prompts");
532
+ const answered = (await text({
533
+ message: `qual o objetivo da task ${manifest.name}?`,
534
+ validate: (v) => (!v || v.trim() === "" ? "objetivo não pode ser vazio" : undefined),
535
+ }));
536
+ if (isCancel(answered)) {
537
+ console.log(dim("cancelado"));
538
+ return;
539
+ }
540
+ goal = answered.trim();
541
+ }
542
+ else {
543
+ throw new Error(`task ${manifest.name} sem objetivo — passe "${bold('auton run "<objetivo>"')}" ou defina 'goal' no task.yaml`);
544
+ }
420
545
  }
421
- // 0.16.13: modelo efetivo = --model > task.yaml > env; valida no provider antes do turno
422
- await precheckModel(model ?? manifest.models ?? process.env.AUTON_MODEL ?? "");
546
+ // 0.17: modelo efetivo = --model > task.yaml > env > config default (auton model)
547
+ const defaultModel = getDefaultModel();
548
+ await precheckModel(model ?? manifest.models ?? process.env.AUTON_MODEL ?? defaultModel);
423
549
  // 0.15.1: credencial do `auton login` injetada no deploy (key/base);
424
550
  // --model (0.14.3) sobrescreve models: do task.yaml
425
551
  await cmdDeploy(dir, { ...credentialEnv(), ...(model ? { AUTON_MODEL: model } : {}) });
426
552
  const lines = await runWithGates(manifest.name, goal);
427
553
  await printTaskDone(manifest.name, goal, lines);
428
554
  }
555
+ /** G2: auton run --async — dispara task e retorna task-id imediatamente. */
556
+ async function cmdRunAsync(dir, goalArg, skipVerify, model) {
557
+ const manifest = await loadTaskManifest(dir);
558
+ if (!skipVerify)
559
+ await verifyPreFlight(dir, manifest);
560
+ let goal = goalArg ?? manifest.goal;
561
+ if (!goal) {
562
+ throw new Error(`task ${manifest.name} sem objetivo — passe "auton run \"<objetivo>\" --async" ou defina 'goal' no task.yaml`);
563
+ }
564
+ const defaultModel = getDefaultModel();
565
+ await precheckModel(model ?? manifest.models ?? process.env.AUTON_MODEL ?? defaultModel);
566
+ await cmdDeploy(dir, { ...credentialEnv(), ...(model ? { AUTON_MODEL: model } : {}) });
567
+ // Gera task-id único: agent-name + timestamp + random
568
+ const taskId = `${manifest.name}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
569
+ // Dispara invoke sem aguardar (fire-and-forget)
570
+ const body = { prompt: goal };
571
+ const res = await fetch(`${API}/v1/agents/${manifest.name}/invoke`, {
572
+ method: "POST",
573
+ headers: authHeaders({ "Content-Type": "application/json" }),
574
+ body: JSON.stringify(body),
575
+ });
576
+ if (!res.ok) {
577
+ throw new Error(`control plane ${res.status}: ${await res.text()}`);
578
+ }
579
+ // Salva mapping task-id -> agent name para wait/result
580
+ const taskMapKey = `task-map:${taskId}`;
581
+ await fetch(`${API}/v1/agents/${manifest.name}/kv/${taskMapKey}`, {
582
+ method: "PUT",
583
+ headers: authHeaders({ "Content-Type": "application/json" }),
584
+ body: JSON.stringify({ agent: manifest.name, goal, startedAt: new Date().toISOString() }),
585
+ });
586
+ console.log(ok(`task iniciada (async): ${bold(taskId)}`));
587
+ console.log(info(` agente: ${manifest.name}`));
588
+ console.log(info(` objetivo: ${goal}`));
589
+ console.log(info(` acompanhe: ${bold(`auton wait ${taskId}`)} | resultado: ${bold(`auton result ${taskId}`)}`));
590
+ }
591
+ /** G2: auton wait <task-id> — aguarda task terminar (poll logs até TASK:DONE/ERR). */
592
+ async function cmdWait(taskId) {
593
+ if (!taskId)
594
+ throw new ExitError(EXIT.USAGE, "uso: auton wait <task-id>");
595
+ // Busca agent name do task-id via KV de qualquer agente (precisa listar agentes)
596
+ const agentsRes = await fetch(`${API}/v1/agents`, { headers: authHeaders() });
597
+ if (!agentsRes.ok)
598
+ throw new Error(`control plane ${agentsRes.status}: ${await agentsRes.text()}`);
599
+ const agents = (await agentsRes.json());
600
+ let agentName;
601
+ let taskInfo;
602
+ for (const a of agents) {
603
+ const taskMapKey = `task-map:${taskId}`;
604
+ const res = await fetch(`${API}/v1/agents/${a.name}/kv/${taskMapKey}`, { headers: authHeaders() });
605
+ if (res.ok) {
606
+ taskInfo = (await res.json());
607
+ agentName = taskInfo.agent;
608
+ break;
609
+ }
610
+ }
611
+ if (!agentName || !taskInfo) {
612
+ throw new Error(`task-id ${taskId} não encontrado`);
613
+ }
614
+ console.log(info(`aguardando task ${bold(taskId)} (agente: ${agentName})...`));
615
+ const deadline = Date.now() + 30 * 60 * 1000; // 30 min max
616
+ let lastLineCount = 0;
617
+ while (Date.now() < deadline) {
618
+ await new Promise((r) => setTimeout(r, 2000));
619
+ const res = await fetch(`${API}/v1/agents/${agentName}/logs?tail=50`, {
620
+ headers: authHeaders({ Accept: "application/json" }),
621
+ });
622
+ if (!res.ok)
623
+ continue;
624
+ const entries = (await res.json());
625
+ const lines = entries.map((e) => e.line);
626
+ // Verifica se terminou
627
+ const doneLine = lines.find((l) => l.startsWith("[TASK:DONE]"));
628
+ const errLine = lines.find((l) => l.startsWith("[TASK:ERR]"));
629
+ if (doneLine || errLine) {
630
+ console.log(ok(`task ${bold(taskId)} concluída`));
631
+ if (doneLine) {
632
+ const ev = JSON.parse(doneLine.slice("[TASK:DONE]".length).trim());
633
+ if (ev.result)
634
+ console.log(dim(`resultado: ${ev.result}`));
635
+ }
636
+ else {
637
+ console.log(fail(`task falhou: ${errLine.slice("[TASK:ERR]".length).trim()}`));
638
+ process.exitCode = EXIT.ERR;
639
+ }
640
+ return;
641
+ }
642
+ // Progress: mostra novas linhas se houver
643
+ if (lines.length > lastLineCount) {
644
+ for (let i = lastLineCount; i < lines.length; i++) {
645
+ const line = lines[i];
646
+ if (line)
647
+ console.log(tagLine(line));
648
+ }
649
+ lastLineCount = lines.length;
650
+ }
651
+ }
652
+ console.log(info(`timeout aguardando task — veja ${bold(`auton logs ${agentName}`)} ou ${bold(`auton result ${taskId}`)}`));
653
+ process.exitCode = EXIT.TIMEOUT;
654
+ }
655
+ /** G2: auton result <task-id> — busca resultado final da task (sem aguardar). */
656
+ async function cmdResult(taskId) {
657
+ if (!taskId)
658
+ throw new ExitError(EXIT.USAGE, "uso: auton result <task-id>");
659
+ const agentsRes = await fetch(`${API}/v1/agents`, { headers: authHeaders() });
660
+ if (!agentsRes.ok)
661
+ throw new Error(`control plane ${agentsRes.status}: ${await agentsRes.text()}`);
662
+ const agents = (await agentsRes.json());
663
+ let agentName;
664
+ for (const a of agents) {
665
+ const taskMapKey = `task-map:${taskId}`;
666
+ const res = await fetch(`${API}/v1/agents/${a.name}/kv/${taskMapKey}`, { headers: authHeaders() });
667
+ if (res.ok) {
668
+ const info = (await res.json());
669
+ agentName = info.agent;
670
+ break;
671
+ }
672
+ }
673
+ if (!agentName) {
674
+ throw new Error(`task-id ${taskId} não encontrado`);
675
+ }
676
+ // Busca última evidência TASK:DONE nos logs
677
+ const res = await fetch(`${API}/v1/agents/${agentName}/logs?tail=100`, {
678
+ headers: authHeaders({ Accept: "application/json" }),
679
+ });
680
+ if (!res.ok)
681
+ throw new Error(`control plane ${res.status}: ${await res.text()}`);
682
+ const entries = (await res.json());
683
+ const lines = entries.map((e) => e.line);
684
+ const doneLine = [...lines].reverse().find((l) => l.startsWith("[TASK:DONE]"));
685
+ const errLine = [...lines].reverse().find((l) => l.startsWith("[TASK:ERR]"));
686
+ if (doneLine) {
687
+ const ev = JSON.parse(doneLine.slice("[TASK:DONE]".length).trim());
688
+ console.log(ok(`task ${bold(taskId)} concluída`));
689
+ console.log(`resultado: ${ev.result ?? "?"}`);
690
+ if (ev.evidence)
691
+ console.log(dim(`evidência: ${JSON.stringify(ev.evidence)}`));
692
+ return;
693
+ }
694
+ if (errLine) {
695
+ console.log(fail(`task ${bold(taskId)} falhou`));
696
+ console.log(`erro: ${errLine.slice("[TASK:ERR]".length).trim()}`);
697
+ process.exitCode = EXIT.ERR;
698
+ return;
699
+ }
700
+ console.log(info(`task ${bold(taskId)} ainda em andamento — use ${bold(`auton wait ${taskId}`)} para aguardar`));
701
+ }
429
702
  /** 0.16.13 — valida modelo no provider antes do turno (ollama /api/tags; openai-compat /models).
430
703
  * Falha dura só quando o provider respondeu e o modelo não existe; endpoint fora → aviso. */
431
704
  async function precheckModel(model) {
432
705
  if (!model)
433
706
  return;
434
- const [provider, name] = model.split(":", 2);
707
+ // 0.17.1 — separa provider do nome SEM cortar a tag (ollama:deepseek-coder:1.3b)
708
+ const sep = model.indexOf(":");
709
+ const provider = sep === -1 ? model : model.slice(0, sep);
710
+ const name = sep === -1 ? undefined : model.slice(sep + 1);
435
711
  if (!name)
436
712
  return;
437
713
  if (provider === "ollama") {
@@ -462,30 +738,37 @@ async function precheckModel(model) {
462
738
  return;
463
739
  }
464
740
  }
465
- /** Roda o turno resolvendo gates pendentes em linha (TTY pergunta; pipe reporta). */
741
+ /** Roda o turno resolvendo gates pendentes em linha (TTY pergunta; pipe reporta).
742
+ * 0.17.0: além de aprovação, resolve perguntas de clarificação (ctx.ask). */
466
743
  async function runWithGates(name, goal) {
467
744
  let lines = await cmdInvoke(name, goal, false);
468
745
  for (let i = 0; i < 10; i++) {
469
- const gate = lines.find((l) => l.startsWith("[APPROVAL:REQ]"));
746
+ const gate = lines.find((l) => l.startsWith("[APPROVAL:REQ]") || l.startsWith("[QUESTION:REQ]"));
470
747
  if (!gate)
471
748
  return lines;
749
+ const g = parseGateLine(gate);
750
+ if (!g)
751
+ return lines;
752
+ if (g.kind === "question") {
753
+ if (!isTTY) {
754
+ console.log(info("pergunta pendente — rode `auton answer <nome> -- \"resposta\"`"));
755
+ return lines;
756
+ }
757
+ const answer = await promptGateQuestion(String(g.payload?.question ?? "pergunta"));
758
+ if (answer === undefined)
759
+ return lines;
760
+ lines = await answerPost(name, answer);
761
+ continue;
762
+ }
472
763
  if (!isTTY) {
473
764
  console.log(info("aprovação pendente — rode `auton approve` ou `auton reject`"));
474
765
  return lines;
475
766
  }
476
- let payload;
477
- try {
478
- payload = gate.slice("[APPROVAL:REQ]".length).trim();
479
- JSON.parse(payload);
480
- }
481
- catch {
482
- payload = "{}";
483
- }
484
- const reason = JSON.parse(payload).reason ?? "ação";
767
+ const reason = g.payload?.reason ?? "ação";
485
768
  const approved = await promptGateApproval(reason);
486
769
  lines = await cmdInvokeApproval(name, approved, undefined);
487
770
  }
488
- throw new Error(`loop de aprovação não convergiu (10 gates)`);
771
+ throw new Error(`loop de gates não convergiu (10)`);
489
772
  }
490
773
  /** POST de controle: resolve o gate pendente no worker (sem novo trigger). */
491
774
  async function cmdInvokeApproval(name, approved, reason) {
@@ -506,6 +789,62 @@ async function cmdInvokeApproval(name, approved, reason) {
506
789
  console.log(line);
507
790
  return out.lines;
508
791
  }
792
+ /** 0.17.0 — POST de controle de pergunta: resolve ctx.ask no worker (sem trigger novo). */
793
+ async function answerPost(name, answer) {
794
+ const res = await fetch(`${API}/v1/agents/${name}/invoke`, {
795
+ method: "POST",
796
+ headers: authHeaders({ "Content-Type": "application/json" }),
797
+ body: JSON.stringify({ question: { answer } }),
798
+ });
799
+ if (!res.ok) {
800
+ const body = await res.text();
801
+ if (res.status === 409 && body.includes("sem pergunta pendente")) {
802
+ throw new Error(`task ${name} sem pergunta pendente`);
803
+ }
804
+ throw new Error(`control plane ${res.status}: ${body}`);
805
+ }
806
+ const out = (await res.json());
807
+ return out.lines;
808
+ }
809
+ /** auton answer [name] [-- texto] — responde pergunta de clarificação pendente. */
810
+ async function cmdAnswer(name, answer) {
811
+ const agent = await resolveAgent("answer", name);
812
+ if (answer === undefined) {
813
+ if (!isTTY) {
814
+ throw new ExitError(EXIT.USAGE, `uso: auton answer <nome> -- "resposta" (pipe)`);
815
+ }
816
+ // mostra a pergunta pendente (logs recentes) e pede a resposta
817
+ let question = "pergunta pendente — responda:";
818
+ const logs = await fetch(`${API}/v1/agents/${agent}/logs?tail=30`, {
819
+ headers: authHeaders({ Accept: "application/json" }),
820
+ });
821
+ if (logs.ok) {
822
+ const entries = (await logs.json());
823
+ const qline = [...entries].reverse().find((e) => e.line.startsWith("[QUESTION:REQ]"));
824
+ if (qline) {
825
+ const g = parseGateLine(qline.line);
826
+ if (g)
827
+ question = String(g.payload?.question ?? question);
828
+ }
829
+ }
830
+ answer = await promptGateQuestion(question);
831
+ if (answer === undefined) {
832
+ console.log(dim("cancelado — agente segue pausado"));
833
+ return;
834
+ }
835
+ }
836
+ const lines = await answerPost(agent, answer);
837
+ console.log(ok(`resposta entregue — task ${bold(agent)} segue.`));
838
+ const done = lines.find((l) => l.startsWith("[TASK:DONE]"));
839
+ if (done) {
840
+ const ev = JSON.parse(done.slice("[TASK:DONE]".length).trim());
841
+ if (ev.result)
842
+ console.log(dim(`resultado: ${ev.result}`));
843
+ }
844
+ else if (lines.some((l) => l.startsWith("[QUESTION:REQ]"))) {
845
+ console.log(info("nova pergunta pendente — `auton answer` de novo."));
846
+ }
847
+ }
509
848
  /** auton continue — retoma a última task sem re-passar o objetivo.
510
849
  * O harness guarda o último input no KV ("ultima-task"); aqui só busca e
511
850
  * invoca de novo (worker dormindo → cold start; KV persiste). */
@@ -531,15 +870,7 @@ async function cmdContinue(dir, nameArg, goalArg) {
531
870
  * 0.12: vira POST de CONTROLE (resolve gate pendente no worker vivo);
532
871
  * gate de legado (template com approval:) também é resolvido assim. */
533
872
  async function cmdApproval(name, approved, reason) {
534
- let agent = name;
535
- if (!agent) {
536
- try {
537
- agent = (await loadTaskManifest(process.cwd())).name;
538
- }
539
- catch {
540
- throw new Error(`${approved ? "approve" : "reject"}: nenhum nome passado e cwd não é um agente — rode \`auton ${approved ? "approve" : "reject"} <nome>\``);
541
- }
542
- }
873
+ const agent = await resolveAgent(approved ? "approve" : "reject", name);
543
874
  const lines = await cmdInvokeApproval(agent, approved, reason);
544
875
  const done = lines.find((l) => l.startsWith("[TASK:DONE]"));
545
876
  if (done) {
@@ -562,7 +893,7 @@ async function cmdApproval(name, approved, reason) {
562
893
  /** 0.16.14: snapshot JSON c/ timestamp + filtro por turno (--turn N = últimos N turnos);
563
894
  * --follow mantém SSE ao vivo (sem ts, como antes). */
564
895
  async function cmdLogs(name, tail, since, follow = false, turn) {
565
- const agent = name ?? (await loadTaskManifest(process.cwd())).name;
896
+ const agent = name ?? (await resolveAgent("logs", undefined));
566
897
  const params = new URLSearchParams();
567
898
  if (tail !== undefined)
568
899
  params.set("tail", String(tail));
@@ -597,6 +928,7 @@ async function cmdLogs(name, tail, since, follow = false, turn) {
597
928
  }
598
929
  if (!res.body)
599
930
  throw new Error("resposta sem body");
931
+ console.log(dim(`— seguindo ${agent} — Ctrl+C sai —`));
600
932
  const reader = res.body.getReader();
601
933
  const decoder = new TextDecoder();
602
934
  let buf = "";
@@ -619,7 +951,7 @@ async function cmdLogs(name, tail, since, follow = false, turn) {
619
951
  }
620
952
  }
621
953
  async function cmdStatus(name) {
622
- const agent = name ?? (await loadTaskManifest(process.cwd())).name;
954
+ const agent = await resolveAgent("status", name);
623
955
  const res = await fetch(`${API}/v1/agents/${agent}`, {
624
956
  headers: authHeaders(),
625
957
  });
@@ -630,7 +962,7 @@ async function cmdStatus(name) {
630
962
  }
631
963
  async function cmdRestart(name) {
632
964
  // 0.1.1 — reinicia o agente (SIGTERM + respawn, state preservado)
633
- const agent = name ?? (await loadTaskManifest(process.cwd())).name;
965
+ const agent = await resolveAgent("restart", name);
634
966
  const res = await fetch(`${API}/v1/agents/${agent}/restart`, {
635
967
  method: "POST",
636
968
  headers: authHeaders(),
@@ -661,7 +993,7 @@ async function cmdUndeploy(name) {
661
993
  console.log(dim("(nada pra remover)"));
662
994
  return;
663
995
  }
664
- const agent = name ?? (await loadTaskManifest(process.cwd())).name;
996
+ const agent = await resolveAgent("undeploy", name);
665
997
  if (isTTY && !(await promptConfirmUndeploy(agent)))
666
998
  return;
667
999
  const res = await fetch(`${API}/v1/agents/${agent}`, {
@@ -819,7 +1151,7 @@ async function cmdInvoke(name, prompt, stream) {
819
1151
  /** autonomous context [name] — imprime a memória do trabalho da task:
820
1152
  * decisions/findings/artifacts gravadas via ctx.work (KV "work:*"). */
821
1153
  async function cmdContext(name) {
822
- const agent = name ?? (await loadTaskManifest(process.cwd())).name;
1154
+ const agent = await resolveAgent("context", name);
823
1155
  const res = await fetch(`${API}/v1/agents/${agent}/kv`, { headers: authHeaders() });
824
1156
  if (!res.ok)
825
1157
  throw new Error(`control plane ${res.status}: ${await res.text()}`);
@@ -916,8 +1248,9 @@ async function cmdPs(json) {
916
1248
  a.cron ? "sim" : "-",
917
1249
  String(a.logs),
918
1250
  String(a.kv),
1251
+ a.model ? String(a.model) : "-",
919
1252
  ]);
920
- console.log(table(["AGENTE", "STATUS", "CRON", "LOGS", "KV"], rows));
1253
+ console.log(table(["AGENTE", "STATUS", "CRON", "LOGS", "KV", "MODEL"], rows));
921
1254
  }
922
1255
  async function cmdKv(op, name, key, value) {
923
1256
  const agent = name ?? (await loadTaskManifest(process.cwd())).name;
@@ -958,40 +1291,101 @@ async function cmdKv(op, name, key, value) {
958
1291
  throw new Error(`control plane ${res.status}: ${await res.text()}`);
959
1292
  console.log(ok(`${key} = ${bold(value)}`));
960
1293
  }
961
- async function cmdLogin(args) {
1294
+ async function cmdLogin(realArgs) {
962
1295
  // 0.15.1 — auton login [provider] [--apikey X] / auton logout
963
- const keyIdx = args.indexOf("--apikey");
964
- const apiKey = keyIdx !== -1 ? args[keyIdx + 1] : undefined;
965
- const provider = args
966
- .filter((a, i) => a !== "--apikey" && args[i - 1] !== "--apikey" && !a.startsWith("-"))
1296
+ const keyIdx = realArgs.indexOf("--apikey");
1297
+ const apiKey = keyIdx !== -1 ? realArgs[keyIdx + 1] : undefined;
1298
+ const provider = realArgs
1299
+ .filter((a, i) => a !== "--apikey" && realArgs[i - 1] !== "--apikey" && !a.startsWith("-"))
967
1300
  .find(() => true);
968
1301
  await loginCmd(provider, apiKey);
969
1302
  }
970
- async function cmdLogout() {
971
- await logoutCmd();
1303
+ async function cmdLogout(providerId) {
1304
+ await logoutCmd(providerId);
972
1305
  }
973
1306
  async function main() {
974
- const args = process.argv.slice(2);
975
- if (args.length === 0 || args[0] === "--help" || args[0] === "-h") {
1307
+ let realArgs = process.argv.slice(2);
1308
+ // G3: auto-start CP para comandos que precisam dele (--no-auto-cp desliga)
1309
+ const noAutoCP = realArgs.includes("--no-auto-cp");
1310
+ const noCP = new Set(["login", "logout", "model", "--help", "-h", "--version", "-v", "init"]);
1311
+ const cmd = realArgs[0];
1312
+ if (!noAutoCP && cmd && !noCP.has(cmd)) {
1313
+ await ensureCPRunning();
1314
+ }
1315
+ // Remove --no-auto-cp dos realArgs antes do switch
1316
+ realArgs = realArgs.filter((a) => a !== "--no-auto-cp");
1317
+ if (realArgs.length === 0) {
1318
+ // 0.17.0 — `auton` bare: abre sessão de trabalho (estilo opencode):
1319
+ // cwd é agente → sessão nele; senão picker. Pipe: mosta ajuda.
1320
+ if (isTTY) {
1321
+ await cmdChat(undefined);
1322
+ return;
1323
+ }
1324
+ console.log(USAGE);
1325
+ return;
1326
+ }
1327
+ if (realArgs[0] === "--help" || realArgs[0] === "-h") {
976
1328
  console.log(USAGE);
977
1329
  return;
978
1330
  }
979
- if (args[0] === "--version" || args[0] === "-v") {
1331
+ if (realArgs[0] === "--version" || realArgs[0] === "-v") {
980
1332
  if (isTTY)
981
1333
  console.log(logo());
982
1334
  console.log(bold(`auton v${VERSION}`));
1335
+ const d = getDefaultModel();
1336
+ const active = getActiveProvider();
1337
+ if (d)
1338
+ console.log(dim(` modelo default: ${d}`));
1339
+ if (active)
1340
+ console.log(dim(` provider: ${active.id} (${active.quando.slice(0, 10)})`));
983
1341
  return;
984
1342
  }
985
- switch (args[0]) {
1343
+ // 0.17.0 — `auton <nome-de-agente-deployado>` abre sessão (comandos têm prioridade).
1344
+ const KNOWN = new Set([
1345
+ "login", "logout", "model", "agent", "logs", "status", "restart", "run",
1346
+ "verify", "inspect", "continue", "invoke", "undeploy", "approve", "reject",
1347
+ "ps", "context", "env", "init", "kv", "chat", "answer", "wait", "result",
1348
+ ]);
1349
+ if (!KNOWN.has(realArgs[0]) && isTTY) {
1350
+ const deployed = await listAgentNames();
1351
+ if (deployed.includes(realArgs[0])) {
1352
+ await cmdChat(realArgs[0]);
1353
+ return;
1354
+ }
1355
+ }
1356
+ switch (realArgs[0]) {
986
1357
  case "login":
987
- await cmdLogin(args.slice(1));
1358
+ await cmdLogin(realArgs.slice(1));
988
1359
  break;
989
1360
  case "logout":
990
- await cmdLogout();
1361
+ await cmdLogout(realArgs[1]);
1362
+ break;
1363
+ case "model": {
1364
+ // auton model [ls|set <spec>|test] — sem sub: seletor TTY
1365
+ const sub = realArgs[1];
1366
+ const json = realArgs.includes("--json");
1367
+ if (sub === "ls") {
1368
+ await modelLs(json);
1369
+ }
1370
+ else if (sub === "set") {
1371
+ if (!realArgs[2])
1372
+ throw new ExitError(EXIT.USAGE, "uso: auton model set <provider:modelo>");
1373
+ await modelSet(realArgs[2]);
1374
+ }
1375
+ else if (sub === "test") {
1376
+ await modelTest();
1377
+ }
1378
+ else if (sub === undefined || sub === "select") {
1379
+ await cmdModel();
1380
+ }
1381
+ else {
1382
+ throw new ExitError(EXIT.USAGE, `uso: auton model [ls|set <spec>|test] — subcomando desconhecido: ${sub}`);
1383
+ }
991
1384
  break;
1385
+ }
992
1386
  case "agent":
993
- if (args[1]) {
994
- const dir = resolve(args[1]);
1387
+ if (realArgs[1]) {
1388
+ const dir = resolve(realArgs[1]);
995
1389
  ensureRuntimeLink(dir);
996
1390
  await cmdDeploy(dir);
997
1391
  }
@@ -1001,8 +1395,8 @@ async function main() {
1001
1395
  }
1002
1396
  break;
1003
1397
  case "logs":
1004
- // args[1..]: [name] [--tail N] [--since RFC3339] [--follow] [--turn N]
1005
- const rest = args.slice(1);
1398
+ // realArgs[1..]: [name] [--tail N] [--since RFC3339] [--follow] [--turn N]
1399
+ const rest = realArgs.slice(1);
1006
1400
  const tailIdx = rest.indexOf("--tail");
1007
1401
  const sinceIdx = rest.indexOf("--since");
1008
1402
  let name;
@@ -1034,34 +1428,90 @@ async function main() {
1034
1428
  await cmdLogs(name, tail, since, follow, turn);
1035
1429
  break;
1036
1430
  case "status":
1037
- await cmdStatus(args[1]);
1431
+ await cmdStatus(realArgs[1]);
1038
1432
  break;
1039
1433
  case "restart":
1040
- await cmdRestart(args[1]);
1434
+ await cmdRestart(realArgs[1]);
1041
1435
  break;
1042
1436
  case "run": {
1043
- // auton run "<goal>" [--skip-verify] [--model X] (ou auton run goal do task.yaml)
1044
- const skipVerify = args.includes("--skip-verify");
1045
- const modelIdx = args.indexOf("--model");
1046
- const model = modelIdx !== -1 ? args[modelIdx + 1] : undefined;
1047
- const goal = args
1048
- .slice(1)
1049
- .filter((a, i) => a !== "--skip-verify" && a !== "--model" && args[i - 1] !== "--model")
1050
- .join(" ")
1051
- .trim() || undefined;
1052
- ensureRuntimeLink(process.cwd());
1053
- await cmdRun(process.cwd(), goal, skipVerify, model);
1437
+ // auton run "<goal>" [--skip-verify] [--model X] [--async] [--dir DIR] [-d DIR] (ou auton run <agent-name> "goal")
1438
+ const skipVerify = realArgs.includes("--skip-verify");
1439
+ const isAsync = realArgs.includes("--async");
1440
+ const modelIdx = realArgs.indexOf("--model");
1441
+ const model = modelIdx !== -1 ? realArgs[modelIdx + 1] : undefined;
1442
+ // --dir / -d
1443
+ const dirIdx = realArgs.indexOf("--dir");
1444
+ const dirShortIdx = realArgs.indexOf("-d");
1445
+ const explicitDir = (dirIdx !== -1 ? realArgs[dirIdx + 1] : undefined) ??
1446
+ (dirShortIdx !== -1 ? realArgs[dirShortIdx + 1] : undefined);
1447
+ // resolve dir: --dir > agent name via CP > cwd
1448
+ let targetDir = explicitDir ? resolve(explicitDir) : process.cwd();
1449
+ let usedAgentName = false;
1450
+ let agentNameSliceIdx = -1; // index in realArgs.slice(1) of the agent name
1451
+ if (!explicitDir) {
1452
+ // Find first positional arg (not a flag) after "run"
1453
+ let firstPosArg;
1454
+ let firstPosArgIdx = -1;
1455
+ for (let i = 1; i < realArgs.length; i++) {
1456
+ const arg = realArgs[i];
1457
+ if (arg && !arg.startsWith("-")) {
1458
+ firstPosArg = arg;
1459
+ firstPosArgIdx = i;
1460
+ break;
1461
+ }
1462
+ }
1463
+ if (firstPosArg) {
1464
+ const knownCmds = new Set([
1465
+ "login", "logout", "model", "agent", "logs", "status", "restart", "run",
1466
+ "verify", "inspect", "continue", "invoke", "undeploy", "approve", "reject",
1467
+ "ps", "context", "env", "init", "kv", "chat", "answer", "wait", "result",
1468
+ ]);
1469
+ if (!knownCmds.has(firstPosArg)) {
1470
+ try {
1471
+ const agent = await resolveAgent("run", firstPosArg);
1472
+ const statusRes = await fetch(`${API}/v1/agents/${agent}`, { headers: authHeaders() });
1473
+ if (statusRes.ok) {
1474
+ const status = (await statusRes.json());
1475
+ if (status.dir) {
1476
+ targetDir = resolve(status.dir);
1477
+ usedAgentName = true;
1478
+ agentNameSliceIdx = firstPosArgIdx - 1; // index in realArgs.slice(1)
1479
+ }
1480
+ }
1481
+ }
1482
+ catch { /* ignora, usa cwd */ }
1483
+ }
1484
+ }
1485
+ }
1486
+ // build goal: exclude flags + agent name if used for resolution
1487
+ const goal = realArgs.slice(1).filter((a, i) => {
1488
+ const origIdx = i + 1; // index in realArgs
1489
+ if (a === "--skip-verify" || a === "--model" || realArgs[origIdx - 1] === "--model" ||
1490
+ a === "--async" || a === "--dir" || a === "-d" ||
1491
+ realArgs[origIdx - 1] === "--dir" || realArgs[origIdx - 1] === "-d")
1492
+ return false;
1493
+ if (usedAgentName && i === agentNameSliceIdx)
1494
+ return false; // exclude agent name from goal
1495
+ return true;
1496
+ }).join(" ").trim() || undefined;
1497
+ ensureRuntimeLink(targetDir);
1498
+ if (isAsync) {
1499
+ await cmdRunAsync(targetDir, goal, skipVerify, model);
1500
+ }
1501
+ else {
1502
+ await cmdRun(targetDir, goal, skipVerify, model);
1503
+ }
1054
1504
  break;
1055
1505
  }
1056
1506
  case "verify": {
1057
- const rest = args.slice(1);
1507
+ const rest = realArgs.slice(1);
1058
1508
  const json = rest.includes("--json");
1059
1509
  const dirArg = rest.find((a) => !a.startsWith("-"));
1060
1510
  await cmdVerify(resolve(dirArg ?? process.cwd()), json);
1061
1511
  break;
1062
1512
  }
1063
1513
  case "inspect": {
1064
- const rest = args.slice(1);
1514
+ const rest = realArgs.slice(1);
1065
1515
  const json = rest.includes("--json");
1066
1516
  const dirArg = rest.find((a) => !a.startsWith("-"));
1067
1517
  await cmdInspect(resolve(dirArg ?? process.cwd()), json);
@@ -1069,51 +1519,74 @@ async function main() {
1069
1519
  }
1070
1520
  case "continue": {
1071
1521
  // auton continue [name] [goal...]
1072
- const goal = args.slice(2).filter((a) => !a.startsWith("-")).join(" ").trim() || undefined;
1073
- const nameArg = args[1] && !args[1].startsWith("-") ? args[1] : undefined;
1522
+ const goal = realArgs.slice(2).filter((a) => !a.startsWith("-")).join(" ").trim() || undefined;
1523
+ const nameArg = realArgs[1] && !realArgs[1].startsWith("-") ? realArgs[1] : undefined;
1074
1524
  ensureRuntimeLink(process.cwd());
1075
1525
  await cmdContinue(process.cwd(), nameArg, goal);
1076
1526
  break;
1077
1527
  }
1078
1528
  case "invoke": {
1079
1529
  // deploy invoke [name] [-- prompt] [--stream]
1080
- const rest = args.slice(2);
1530
+ const rest = realArgs.slice(2);
1081
1531
  const stream = rest.includes("--stream");
1082
1532
  const promptIdx = rest.indexOf("--");
1083
1533
  const prompt = promptIdx !== -1 ? rest.slice(promptIdx + 1).join(" ") : undefined;
1084
- await cmdInvoke(args[1], prompt, stream);
1534
+ await cmdInvoke(realArgs[1], prompt, stream);
1085
1535
  break;
1086
1536
  }
1087
1537
  case "undeploy":
1088
- await cmdUndeploy(args[1]);
1538
+ await cmdUndeploy(realArgs[1]);
1539
+ break;
1540
+ case "wait":
1541
+ // auton wait <task-id> [--timeout Ms]
1542
+ if (!realArgs[1])
1543
+ throw new ExitError(EXIT.USAGE, "uso: auton wait <task-id>");
1544
+ await cmdWait(realArgs[1]);
1545
+ break;
1546
+ case "result":
1547
+ // auton result <task-id>
1548
+ if (!realArgs[1])
1549
+ throw new ExitError(EXIT.USAGE, "uso: auton result <task-id>");
1550
+ await cmdResult(realArgs[1]);
1089
1551
  break;
1090
1552
  case "approve":
1091
1553
  case "reject": {
1092
- const approved = args[0] === "approve";
1554
+ const approved = realArgs[0] === "approve";
1093
1555
  // auton approve [name] [-- razão]: -- na posição [1] = sem name
1094
- const rest = args.slice(1);
1556
+ const rest = realArgs.slice(1);
1095
1557
  const dash = rest.indexOf("--");
1096
1558
  const name = dash === -1 ? rest[0] : dash === 0 ? undefined : rest[0];
1097
1559
  const reason = dash !== -1 ? rest.slice(dash + 1).join(" ") : undefined;
1098
1560
  await cmdApproval(name, approved, reason);
1099
1561
  break;
1100
1562
  }
1563
+ case "chat":
1564
+ await cmdChat(realArgs[1]);
1565
+ break;
1566
+ case "answer": {
1567
+ const rest = realArgs.slice(1);
1568
+ const dash = rest.indexOf("--");
1569
+ const name = dash === -1 ? rest[0] : dash === 0 ? undefined : rest[0];
1570
+ const answer = dash !== -1 ? rest.slice(dash + 1).join(" ") : undefined;
1571
+ await cmdAnswer(name, answer);
1572
+ break;
1573
+ }
1101
1574
  case "ps":
1102
- await cmdPs(args[1] === "--json");
1575
+ await cmdPs(realArgs[1] === "--json");
1103
1576
  break;
1104
1577
  case "context":
1105
- await cmdContext(args[1]);
1578
+ await cmdContext(realArgs[1]);
1106
1579
  break;
1107
1580
  case "env": {
1108
- const sub = args[1];
1581
+ const sub = realArgs[1];
1109
1582
  if (sub === "set") {
1110
- await cmdEnvSet(args[2], args[3], args[4]);
1583
+ await cmdEnvSet(realArgs[2], realArgs[3], realArgs[4]);
1111
1584
  }
1112
1585
  else if (sub === "get") {
1113
- await cmdEnvGet(args[2], args[3]);
1586
+ await cmdEnvGet(realArgs[2], realArgs[3]);
1114
1587
  }
1115
1588
  else if (sub === "ls") {
1116
- await cmdEnvLs(args[2]);
1589
+ await cmdEnvLs(realArgs[2]);
1117
1590
  }
1118
1591
  else {
1119
1592
  console.log(USAGE);
@@ -1122,8 +1595,8 @@ async function main() {
1122
1595
  }
1123
1596
  case "init": {
1124
1597
  // deploy init [nome] [template] [--mode=X] [--skills=a,b] — sem argumentos (TTY): pergunta interativo
1125
- const initFlags = args.filter((a) => a.startsWith("--"));
1126
- const initPos = args.filter((a) => !a.startsWith("--"));
1598
+ const initFlags = realArgs.filter((a) => a.startsWith("--"));
1599
+ const initPos = realArgs.filter((a) => !a.startsWith("--"));
1127
1600
  const initOpts = {
1128
1601
  mode: initFlags.find((f) => f.startsWith("--mode="))?.slice(7),
1129
1602
  skills: initFlags
@@ -1147,10 +1620,10 @@ async function main() {
1147
1620
  }
1148
1621
  case "kv":
1149
1622
  // deploy kv get|set|ls <nome> [chave] [valor]
1150
- await cmdKv(args[1], args[2], args[3], args[4]);
1623
+ await cmdKv(realArgs[1], realArgs[2], realArgs[3], realArgs[4]);
1151
1624
  break;
1152
1625
  default: {
1153
- console.error(fail(`comando desconhecido: ${args[0]}`));
1626
+ console.error(fail(`comando desconhecido: ${realArgs[0]}`));
1154
1627
  console.error(USAGE);
1155
1628
  process.exit(EXIT.USAGE);
1156
1629
  }