@mcp-graph-workflow/agent-graph-flow 0.2.0 → 0.4.0

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.
Files changed (2) hide show
  1. package/dist/cli/index.js +485 -101
  2. package/package.json +1 -1
package/dist/cli/index.js CHANGED
@@ -11,9 +11,9 @@ import { randomUUID, randomBytes, createHash } from 'crypto';
11
11
  import os, { homedir } from 'os';
12
12
  import { stat, readFile, access, constants } from 'fs/promises';
13
13
  import { execFile, execSync, spawn } from 'child_process';
14
- import { render, useApp, Box, Text } from 'ink';
14
+ import { render, useApp, useInput, Box, Text } from 'ink';
15
15
  import { jsx, jsxs } from 'react/jsx-runtime';
16
- import { createElement, useState, useCallback, useEffect } from 'react';
16
+ import { createElement, useState, useEffect, useCallback } from 'react';
17
17
  import TextInput from 'ink-text-input';
18
18
  import Spinner from 'ink-spinner';
19
19
  import { Command } from 'commander';
@@ -6839,9 +6839,15 @@ var init_copilot_sdk_adapter = __esm({
6839
6839
  init_logger();
6840
6840
  log22 = createLogger({ layer: "core", source: "copilot-sdk-adapter.ts" });
6841
6841
  ModelAdapterError = class extends McpGraphError {
6842
- constructor(message) {
6842
+ /** HTTP status quando a falha veio de uma resposta do provider (p/ classifyLlmError). */
6843
+ status;
6844
+ /** Espera sugerida (ms), derivada de `retry-after` em respostas 429. */
6845
+ retryAfterMs;
6846
+ constructor(message, opts = {}) {
6843
6847
  super(`Model adapter error: ${message}`);
6844
6848
  this.name = "ModelAdapterError";
6849
+ this.status = opts.status;
6850
+ this.retryAfterMs = opts.retryAfterMs;
6845
6851
  }
6846
6852
  };
6847
6853
  CopilotSdkAdapter = class {
@@ -6932,9 +6938,16 @@ var init_copilot_api_adapter = __esm({
6932
6938
  }
6933
6939
  if (!res.ok) {
6934
6940
  if (res.status === 401 || res.status === 403) {
6935
- throw new ModelAdapterError("token Copilot expirado/inv\xE1lido (401/403) \u2014 rode `agf login` novamente.");
6941
+ throw new ModelAdapterError("token Copilot expirado/inv\xE1lido (401/403) \u2014 rode `agf login` novamente.", {
6942
+ status: res.status
6943
+ });
6936
6944
  }
6937
- throw new ModelAdapterError(`API do Copilot retornou ${res.status}: ${(await res.text()).slice(0, 200)}`);
6945
+ const retryAfterRaw = res.headers?.get?.("retry-after");
6946
+ const retryAfterMs = retryAfterRaw && Number.isFinite(Number(retryAfterRaw)) ? Math.round(Number(retryAfterRaw) * 1e3) : void 0;
6947
+ throw new ModelAdapterError(`API do Copilot retornou ${res.status}: ${(await res.text()).slice(0, 200)}`, {
6948
+ status: res.status,
6949
+ retryAfterMs
6950
+ });
6938
6951
  }
6939
6952
  const body = await res.json();
6940
6953
  const text = body.choices?.[0]?.message?.content;
@@ -7078,8 +7091,8 @@ var init_implementation_executor = __esm({
7078
7091
  };
7079
7092
  defaultRunner = (command, cwd) => {
7080
7093
  try {
7081
- const output16 = execSync(command, { cwd, encoding: "utf8", stdio: "pipe" });
7082
- return { exitCode: 0, output: output16 };
7094
+ const output17 = execSync(command, { cwd, encoding: "utf8", stdio: "pipe" });
7095
+ return { exitCode: 0, output: output17 };
7083
7096
  } catch (err) {
7084
7097
  const e = err;
7085
7098
  return {
@@ -7159,7 +7172,83 @@ var init_plan_parser = __esm({
7159
7172
  }
7160
7173
  });
7161
7174
 
7175
+ // src/core/model-hub/llm-error.ts
7176
+ function readHeader(headers, name) {
7177
+ if (!headers || typeof headers !== "object") return null;
7178
+ const getter = headers.get;
7179
+ if (typeof getter === "function") {
7180
+ const v = getter.call(headers, name);
7181
+ return typeof v === "string" ? v : null;
7182
+ }
7183
+ const lower = name.toLowerCase();
7184
+ for (const [k, v] of Object.entries(headers)) {
7185
+ if (k.toLowerCase() === lower && typeof v === "string") return v;
7186
+ }
7187
+ return null;
7188
+ }
7189
+ function extractStatus(err) {
7190
+ const candidates = [err.status, err.statusCode, err.response?.status];
7191
+ for (const c of candidates) {
7192
+ const n = typeof c === "number" ? c : typeof c === "string" ? Number(c) : NaN;
7193
+ if (Number.isFinite(n) && n >= 100 && n < 600) return n;
7194
+ }
7195
+ if (typeof err.message === "string") {
7196
+ const m = err.message.match(/\b(4\d\d|5\d\d)\b/);
7197
+ if (m) return Number(m[1]);
7198
+ }
7199
+ return void 0;
7200
+ }
7201
+ function resolveRetryAfterMs(err) {
7202
+ if (typeof err.retryAfterMs === "number" && err.retryAfterMs >= 0) return err.retryAfterMs;
7203
+ const headers = err.headers ?? err.response?.headers;
7204
+ const raw = readHeader(headers, "retry-after");
7205
+ if (raw !== null) {
7206
+ const seconds = Number(raw);
7207
+ if (Number.isFinite(seconds) && seconds >= 0) return Math.round(seconds * 1e3);
7208
+ }
7209
+ return DEFAULT_RATE_LIMIT_MS;
7210
+ }
7211
+ function classifyLlmError(err) {
7212
+ const e = err && typeof err === "object" ? err : {};
7213
+ const message = typeof e.message === "string" ? e.message : "";
7214
+ const status = extractStatus(e);
7215
+ if (status === 429) {
7216
+ return { kind: "rate_limit", retryable: true, retryAfterMs: resolveRetryAfterMs(e) };
7217
+ }
7218
+ if (status === 401 || status === 403) {
7219
+ return { kind: "auth", retryable: false };
7220
+ }
7221
+ if (status !== void 0 && status >= 400 && status < 500 && CONTENT_POLICY_RE.test(message)) {
7222
+ return { kind: "content_policy", retryable: false };
7223
+ }
7224
+ if (status !== void 0 && status >= 400 && status < 500) {
7225
+ return { kind: "invalid_request", retryable: false };
7226
+ }
7227
+ if (status !== void 0 && RETRYABLE_5XX.has(status)) {
7228
+ return { kind: "server", retryable: true };
7229
+ }
7230
+ const code = typeof e.code === "string" ? e.code : "";
7231
+ if (NETWORK_CODE.has(code) || status === void 0 && NETWORK_MSG_RE.test(message)) {
7232
+ return { kind: "network", retryable: true };
7233
+ }
7234
+ return { kind: "unknown", retryable: false };
7235
+ }
7236
+ var DEFAULT_RATE_LIMIT_MS, CONTENT_POLICY_RE, NETWORK_CODE, NETWORK_MSG_RE, RETRYABLE_5XX;
7237
+ var init_llm_error = __esm({
7238
+ "src/core/model-hub/llm-error.ts"() {
7239
+ init_esm_shims();
7240
+ DEFAULT_RATE_LIMIT_MS = 1e3;
7241
+ CONTENT_POLICY_RE = /content[_\s-]?filter|content[_\s-]?policy|policy violation|moderat/i;
7242
+ NETWORK_CODE = /* @__PURE__ */ new Set(["ECONNRESET", "ETIMEDOUT", "ECONNREFUSED", "EAI_AGAIN", "EPIPE", "ENOTFOUND"]);
7243
+ NETWORK_MSG_RE = /fetch failed|network|socket hang up|timed? out|timeout|aborted|abort/i;
7244
+ RETRYABLE_5XX = /* @__PURE__ */ new Set([500, 502, 503, 504, 529]);
7245
+ }
7246
+ });
7247
+
7162
7248
  // src/core/autonomy/implement-attempt.ts
7249
+ function defaultSleep(ms) {
7250
+ return new Promise((resolve2) => setTimeout(resolve2, ms));
7251
+ }
7163
7252
  function buildInitialPrompt(node, opts = {}) {
7164
7253
  const repoMapBlock = opts.repoMap && opts.repoMap.length > 0 ? [`Contexto do reposit\xF3rio (refer\xEAncia, n\xE3o reescreva o que j\xE1 existe):`, opts.repoMap, ""] : [];
7165
7254
  const flowBlock = opts.flowContext && opts.flowContext.length > 0 ? [opts.flowContext, ""] : [];
@@ -7191,23 +7280,52 @@ function buildRetryPrompt(node, failure, maxFeedbackChars) {
7191
7280
  async function attemptImplementation(deps, options) {
7192
7281
  const maxAttempts = Math.max(1, options.maxAttempts);
7193
7282
  const maxFeedbackChars = options.maxFeedbackChars ?? DEFAULT_MAX_FEEDBACK_CHARS;
7283
+ const sleep2 = deps.sleep ?? defaultSleep;
7194
7284
  let lastResult;
7195
7285
  let lastError;
7196
7286
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
7197
7287
  const prompt = attempt === 1 || !lastResult ? buildInitialPrompt(options.node, { repoMap: options.repoMap, flowContext: options.flowContext }) : buildRetryPrompt(options.node, lastResult, maxFeedbackChars);
7288
+ let text;
7289
+ try {
7290
+ text = await deps.generate(prompt);
7291
+ } catch (err) {
7292
+ lastError = getErrorMessage(err);
7293
+ const cls = classifyLlmError(err);
7294
+ if (!cls.retryable) {
7295
+ log25.warn("Erro permanente do provider \u2014 escalando sem re-tentar", {
7296
+ attempt,
7297
+ node: options.node.id,
7298
+ kind: cls.kind,
7299
+ error: lastError
7300
+ });
7301
+ return { success: false, attempts: attempt, lastResult, error: lastError };
7302
+ }
7303
+ if (cls.retryAfterMs && cls.retryAfterMs > 0) await sleep2(cls.retryAfterMs);
7304
+ log25.warn("Erro transit\xF3rio do provider \u2014 re-tentando", {
7305
+ attempt,
7306
+ node: options.node.id,
7307
+ kind: cls.kind,
7308
+ retryAfterMs: cls.retryAfterMs
7309
+ });
7310
+ continue;
7311
+ }
7198
7312
  let plan;
7199
7313
  try {
7200
- const text = await deps.generate(prompt);
7201
7314
  plan = parseImplementationPlan(text);
7202
7315
  } catch (err) {
7203
- lastError = err instanceof Error ? err.message : String(err);
7316
+ lastError = getErrorMessage(err);
7204
7317
  log25.warn("Tentativa falhou no parse", { attempt, node: options.node.id, error: lastError });
7205
7318
  continue;
7206
7319
  }
7207
7320
  lastResult = await deps.execute(plan);
7208
7321
  if (lastResult.testPassed === true) {
7209
7322
  log25.info("Implementa\xE7\xE3o verde", { attempt, node: options.node.id });
7210
- return { success: true, attempts: attempt, lastResult };
7323
+ const appliedEdits = (plan.edits ?? []).map((e) => ({
7324
+ path: e.path,
7325
+ oldString: e.oldString,
7326
+ newString: e.newString
7327
+ }));
7328
+ return { success: true, attempts: attempt, lastResult, appliedEdits };
7211
7329
  }
7212
7330
  lastError = lastResult.testOutput;
7213
7331
  log25.warn("Testes vermelhos", { attempt, node: options.node.id });
@@ -7219,13 +7337,88 @@ var init_implement_attempt = __esm({
7219
7337
  "src/core/autonomy/implement-attempt.ts"() {
7220
7338
  init_esm_shims();
7221
7339
  init_logger();
7340
+ init_errors();
7222
7341
  init_truncate();
7223
7342
  init_plan_parser();
7343
+ init_llm_error();
7224
7344
  log25 = createLogger({ layer: "core", source: "implement-attempt.ts" });
7225
7345
  DEFAULT_MAX_FEEDBACK_CHARS = 1200;
7226
7346
  }
7227
7347
  });
7228
7348
 
7349
+ // src/core/autonomy/exec-policy.ts
7350
+ function norm(cmd) {
7351
+ return cmd.trim().replace(/\s+/g, " ");
7352
+ }
7353
+ function evaluateExecPolicy(command, rules, defaultEffect = "ask") {
7354
+ const cmd = norm(command);
7355
+ let best;
7356
+ for (const rule of rules) {
7357
+ const m = norm(rule.match);
7358
+ if (cmd === m || cmd.startsWith(m + " ") || cmd.startsWith(m)) {
7359
+ if (!best || m.length > norm(best.match).length) best = rule;
7360
+ }
7361
+ }
7362
+ if (best) return { effect: best.effect, matchedRule: best };
7363
+ const low = cmd.toLowerCase();
7364
+ for (const danger of DEFAULT_DENY) {
7365
+ if (low.includes(danger)) return { effect: "deny", builtin: true };
7366
+ }
7367
+ return { effect: defaultEffect };
7368
+ }
7369
+ function guardExecRunner(base, opts = {}) {
7370
+ const rules = opts.rules ?? [];
7371
+ const defaultEffect = opts.defaultEffect ?? "ask";
7372
+ return (command, cwd) => {
7373
+ const decision = evaluateExecPolicy(command, rules, defaultEffect);
7374
+ const allowed = decision.effect === "allow" || decision.effect === "ask" && opts.cache?.isApproved(command) === true;
7375
+ if (allowed) return base(command, cwd);
7376
+ const reason = decision.effect === "deny" ? decision.builtin ? "deny (built-in perigoso)" : "deny (regra)" : "ask (n\xE3o aprovado nesta sess\xE3o)";
7377
+ return { exitCode: 126, output: `[exec-policy] comando bloqueado \u2014 ${reason}: ${command}` };
7378
+ };
7379
+ }
7380
+ var DEFAULT_DENY;
7381
+ var init_exec_policy = __esm({
7382
+ "src/core/autonomy/exec-policy.ts"() {
7383
+ init_esm_shims();
7384
+ DEFAULT_DENY = [
7385
+ "rm -rf",
7386
+ "sudo ",
7387
+ "git push --force",
7388
+ "git push -f",
7389
+ "chmod -R 777",
7390
+ "chmod 777",
7391
+ "dd if=",
7392
+ ":(){",
7393
+ "mkfs",
7394
+ "| sh",
7395
+ "|sh",
7396
+ "| bash",
7397
+ "> /dev/sd"
7398
+ ];
7399
+ }
7400
+ });
7401
+
7402
+ // src/tui/diff-render.ts
7403
+ function renderEditDiff(edit) {
7404
+ const lines = [`\u2500\u2500 ${edit.path} \u2500\u2500`];
7405
+ if (edit.oldString.length > 0) {
7406
+ for (const l of edit.oldString.split("\n")) lines.push(`- ${l}`);
7407
+ }
7408
+ if (edit.newString.length > 0) {
7409
+ for (const l of edit.newString.split("\n")) lines.push(`+ ${l}`);
7410
+ }
7411
+ return lines;
7412
+ }
7413
+ function renderPlanDiff(edits) {
7414
+ return edits.flatMap(renderEditDiff);
7415
+ }
7416
+ var init_diff_render = __esm({
7417
+ "src/tui/diff-render.ts"() {
7418
+ init_esm_shims();
7419
+ }
7420
+ });
7421
+
7229
7422
  // src/core/code/code-store.ts
7230
7423
  function rowToSymbol(row) {
7231
7424
  return {
@@ -8467,6 +8660,18 @@ function buildLiveImplement(options) {
8467
8660
  onLog?.(`[live] provider: ${resolved.kind === "api" ? "Copilot API (HTTP, logado)" : "Copilot CLI"}`);
8468
8661
  const client = new TieredModelClient(resolved.adapter, config);
8469
8662
  const maxAttempts = Math.max(1, retries);
8663
+ let execRules = [];
8664
+ let execDefault = "allow";
8665
+ const rawPolicy = store2.getProjectSetting("exec_policy");
8666
+ if (rawPolicy) {
8667
+ try {
8668
+ const parsed = JSON.parse(rawPolicy);
8669
+ if (parsed.default) execDefault = parsed.default;
8670
+ if (Array.isArray(parsed.rules)) execRules = parsed.rules;
8671
+ } catch {
8672
+ }
8673
+ }
8674
+ const guardedRunner = guardExecRunner(defaultRunner, { rules: execRules, defaultEffect: execDefault });
8470
8675
  const codeStore = new CodeStore(store2.getDb());
8471
8676
  const projectId = store2.getProject()?.id;
8472
8677
  const repoSymbols = projectId ? codeStore.getAllSymbols(projectId) : [];
@@ -8494,7 +8699,7 @@ function buildLiveImplement(options) {
8494
8699
  });
8495
8700
  return res.text;
8496
8701
  },
8497
- execute: (plan) => executePlan(plan, { workspaceDir: dir, defaultTestCommand: testCmd })
8702
+ execute: (plan) => executePlan(plan, { workspaceDir: dir, defaultTestCommand: testCmd, runCommand: guardedRunner })
8498
8703
  },
8499
8704
  { node, maxAttempts, repoMap, flowContext }
8500
8705
  );
@@ -8503,6 +8708,9 @@ function buildLiveImplement(options) {
8503
8708
  onLog?.(
8504
8709
  ` [live] ${client.modelFor("implement")}: ${outcome.attempts} tentativa(s), ${files} arquivo(s), ${task.total} tok \u2192 ${outcome.success ? "verde" : "escala"}`
8505
8710
  );
8711
+ if (outcome.success && outcome.appliedEdits && outcome.appliedEdits.length > 0) {
8712
+ for (const line of renderPlanDiff(outcome.appliedEdits)) onLog?.(line);
8713
+ }
8506
8714
  try {
8507
8715
  const applied = outcome.lastResult?.applied ?? [];
8508
8716
  insertEpisodicOutcome(store2.getDb(), {
@@ -8530,6 +8738,8 @@ var init_live_implement = __esm({
8530
8738
  init_resolve_adapter();
8531
8739
  init_implementation_executor();
8532
8740
  init_implement_attempt();
8741
+ init_exec_policy();
8742
+ init_diff_render();
8533
8743
  init_code_store();
8534
8744
  init_repo_map();
8535
8745
  init_flow_compact();
@@ -8568,6 +8778,18 @@ var init_store_port = __esm({
8568
8778
  init_definition_of_done();
8569
8779
  }
8570
8780
  });
8781
+
8782
+ // src/tui/status-line.ts
8783
+ function formatStatusLine(input) {
8784
+ const tokens = `${Math.max(0, Math.round(input.totalTokens))} tok`;
8785
+ const cost = `$${input.costUsd.toFixed(4)}`;
8786
+ return `\u26C1 ${tokens} \xB7 ${cost} \xB7 ${input.model}`;
8787
+ }
8788
+ var init_status_line = __esm({
8789
+ "src/tui/status-line.ts"() {
8790
+ init_esm_shims();
8791
+ }
8792
+ });
8571
8793
  function TaskRow({ task }) {
8572
8794
  return /* @__PURE__ */ jsxs(Text, { children: [
8573
8795
  " ",
@@ -8594,6 +8816,7 @@ function App({ model }) {
8594
8816
  " ",
8595
8817
  model.wip
8596
8818
  ] }) }),
8819
+ /* @__PURE__ */ jsx(Box, { paddingX: 1, children: /* @__PURE__ */ jsx(Text, { color: "green", children: formatStatusLine({ totalTokens: model.tokens.total, costUsd: model.tokens.costUsd, model: model.modelLabel }) }) }),
8597
8820
  /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginTop: 1, children: [
8598
8821
  /* @__PURE__ */ jsxs(Text, { bold: true, children: [
8599
8822
  "Tasks ativas (",
@@ -8624,6 +8847,7 @@ var STATUS_ICON;
8624
8847
  var init_app = __esm({
8625
8848
  "src/tui/app.tsx"() {
8626
8849
  init_esm_shims();
8850
+ init_status_line();
8627
8851
  STATUS_ICON = {
8628
8852
  in_progress: "\u25CF",
8629
8853
  ready: "\u25CB",
@@ -8674,6 +8898,7 @@ var init_banner_screen = __esm({
8674
8898
  function CommandBar({ value, onChange, onSubmit, suggestions }) {
8675
8899
  return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
8676
8900
  suggestions.length > 0 && /* @__PURE__ */ jsx(Box, { flexDirection: "column", marginBottom: 1, children: suggestions.map((c) => /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
8901
+ c.source === "skill" ? /* @__PURE__ */ jsx(Text, { color: "magenta", children: "[skill] " }) : null,
8677
8902
  c.usage,
8678
8903
  " \u2014 ",
8679
8904
  c.desc
@@ -8690,6 +8915,44 @@ var init_command_bar = __esm({
8690
8915
  }
8691
8916
  });
8692
8917
 
8918
+ // src/tui/history.ts
8919
+ function valueAt(state, cursor) {
8920
+ if (cursor < 0) return state.draft;
8921
+ return state.history[state.history.length - 1 - cursor];
8922
+ }
8923
+ function navigateHistory(state, dir) {
8924
+ const last = state.history.length - 1;
8925
+ if (last < 0) return { value: state.draft, cursor: -1 };
8926
+ let cursor = state.cursor;
8927
+ if (dir === "up") {
8928
+ cursor = Math.min(cursor + 1, last);
8929
+ } else {
8930
+ cursor = Math.max(cursor - 1, -1);
8931
+ }
8932
+ return { value: valueAt(state, cursor), cursor };
8933
+ }
8934
+ var init_history = __esm({
8935
+ "src/tui/history.ts"() {
8936
+ init_esm_shims();
8937
+ }
8938
+ });
8939
+
8940
+ // src/tui/elapsed.ts
8941
+ function formatElapsed(ms) {
8942
+ const totalSec = Math.max(0, Math.floor(ms / 1e3));
8943
+ const h = Math.floor(totalSec / 3600);
8944
+ const m = Math.floor(totalSec % 3600 / 60);
8945
+ const s = totalSec % 60;
8946
+ if (h > 0) return `${h}h ${String(m).padStart(2, "0")}m`;
8947
+ if (m > 0) return `${m}m ${String(s).padStart(2, "0")}s`;
8948
+ return `${s}s`;
8949
+ }
8950
+ var init_elapsed = __esm({
8951
+ "src/tui/elapsed.ts"() {
8952
+ init_esm_shims();
8953
+ }
8954
+ });
8955
+
8693
8956
  // src/tui/dispatch.ts
8694
8957
  function parseCommand(input) {
8695
8958
  const trimmed = input.trim();
@@ -8699,11 +8962,38 @@ function parseCommand(input) {
8699
8962
  if (sp === -1) return { cmd: body.toLowerCase(), args: "" };
8700
8963
  return { cmd: body.slice(0, sp).toLowerCase(), args: body.slice(sp + 1).trim() };
8701
8964
  }
8965
+ function fuzzyScore(query, text) {
8966
+ const q = query.toLowerCase();
8967
+ const t = text.toLowerCase();
8968
+ if (q === "") return 0;
8969
+ let qi = 0;
8970
+ let score = 0;
8971
+ let lastMatch = -1;
8972
+ for (let ti = 0; ti < t.length && qi < q.length; ti++) {
8973
+ if (t[ti] === q[qi]) {
8974
+ if (lastMatch === -1) score += ti;
8975
+ else score += ti - lastMatch - 1;
8976
+ lastMatch = ti;
8977
+ qi++;
8978
+ }
8979
+ }
8980
+ return qi === q.length ? score : null;
8981
+ }
8982
+ function fuzzyFilter(query, commands) {
8983
+ if (query.trim() === "") return [...commands];
8984
+ const scored = [];
8985
+ commands.forEach((cmd, idx) => {
8986
+ const score = fuzzyScore(query, cmd.name);
8987
+ if (score !== null) scored.push({ cmd, score, idx });
8988
+ });
8989
+ scored.sort((a, b) => a.score !== b.score ? a.score - b.score : a.idx - b.idx);
8990
+ return scored.map((s) => s.cmd);
8991
+ }
8702
8992
  function filterCommands(input, extra = []) {
8703
8993
  const trimmed = input.trim();
8704
8994
  if (!trimmed.startsWith("/")) return [];
8705
- const prefix = trimmed.slice(1).split(" ")[0].toLowerCase();
8706
- return [...COMMANDS, ...extra].filter((c) => c.name.startsWith(prefix));
8995
+ const query = trimmed.slice(1).split(" ")[0];
8996
+ return fuzzyFilter(query, [...COMMANDS, ...extra]);
8707
8997
  }
8708
8998
  async function runAsyncCommand(port, parsed, _onLine) {
8709
8999
  switch (parsed.cmd) {
@@ -8802,13 +9092,36 @@ function InteractiveApp({ dashboard, port, asyncPort, liveRunner, skillCommands
8802
9092
  const [input, setInput] = useState("");
8803
9093
  const [log48, setLog] = useState([]);
8804
9094
  const [running, setRunning] = useState(false);
9095
+ const [elapsedMs, setElapsedMs] = useState(0);
8805
9096
  const [showHelp, setShowHelp] = useState(false);
9097
+ useEffect(() => {
9098
+ if (!running) return;
9099
+ setElapsedMs(0);
9100
+ const startedAt = Date.now();
9101
+ const t = setInterval(() => setElapsedMs(Date.now() - startedAt), 1e3);
9102
+ return () => clearInterval(t);
9103
+ }, [running]);
9104
+ const [history, setHistory] = useState([]);
9105
+ const [histCursor, setHistCursor] = useState(-1);
9106
+ const [draft, setDraft] = useState("");
8806
9107
  const append = (line) => setLog((prev) => [...prev, line].slice(-MAX_LOG_LINES));
9108
+ useInput((_input, key) => {
9109
+ if (phase !== "dashboard" || running) return;
9110
+ if (!key.upArrow && !key.downArrow) return;
9111
+ const effectiveDraft = histCursor === -1 ? input : draft;
9112
+ if (histCursor === -1) setDraft(input);
9113
+ const result = navigateHistory({ history, cursor: histCursor, draft: effectiveDraft }, key.upArrow ? "up" : "down");
9114
+ setHistCursor(result.cursor);
9115
+ setInput(result.value);
9116
+ });
8807
9117
  const submit = (raw) => {
8808
9118
  const parsed = parseCommand(raw);
8809
9119
  setInput("");
8810
9120
  const text = raw.trim();
8811
9121
  if (text === "" || running) return;
9122
+ setHistory((prev) => [...prev, text]);
9123
+ setHistCursor(-1);
9124
+ setDraft("");
8812
9125
  if (parsed.cmd === "quit") {
8813
9126
  exit();
8814
9127
  return;
@@ -8860,7 +9173,10 @@ ${skill.body}` : `Skill n\xE3o encontrada: ${parsed.cmd}`);
8860
9173
  ] }),
8861
9174
  running && /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsxs(Text, { color: "yellow", children: [
8862
9175
  /* @__PURE__ */ jsx(Spinner, { type: "dots" }),
8863
- " executando\u2026"
9176
+ " executando\u2026 ",
9177
+ formatElapsed(elapsedMs),
9178
+ " \xB7 ",
9179
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Esc para interromper" })
8864
9180
  ] }) }),
8865
9181
  /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(CommandBar, { value: input, onChange: setInput, onSubmit: submit, suggestions: filterCommands(input, skillCommands) }) })
8866
9182
  ] });
@@ -8872,6 +9188,8 @@ var init_interactive_app = __esm({
8872
9188
  init_app();
8873
9189
  init_banner_screen();
8874
9190
  init_command_bar();
9191
+ init_history();
9192
+ init_elapsed();
8875
9193
  init_dispatch();
8876
9194
  MAX_LOG_LINES = 12;
8877
9195
  }
@@ -9522,15 +9840,15 @@ function meanPoolAndNormalize(data, validTokens, dim) {
9522
9840
  embedding[dVar] += data[tVar * dim + dVar];
9523
9841
  }
9524
9842
  }
9525
- let norm = 0;
9843
+ let norm2 = 0;
9526
9844
  for (let dVar = 0; dVar < dim; dVar++) {
9527
9845
  embedding[dVar] /= validTokens;
9528
- norm += embedding[dVar] * embedding[dVar];
9846
+ norm2 += embedding[dVar] * embedding[dVar];
9529
9847
  }
9530
- norm = Math.sqrt(norm);
9531
- if (norm > 0) {
9848
+ norm2 = Math.sqrt(norm2);
9849
+ if (norm2 > 0) {
9532
9850
  for (let dVar = 0; dVar < dim; dVar++) {
9533
- embedding[dVar] /= norm;
9851
+ embedding[dVar] /= norm2;
9534
9852
  }
9535
9853
  }
9536
9854
  return embedding;
@@ -10403,7 +10721,8 @@ async function launchTui(store2) {
10403
10721
  return skills.map((s) => ({
10404
10722
  name: s.name,
10405
10723
  usage: `/${s.name}`,
10406
- desc: `[${s.category}] ${s.description}`
10724
+ desc: `[${s.category}] ${s.description}`,
10725
+ source: "skill"
10407
10726
  }));
10408
10727
  });
10409
10728
  const instance = render(
@@ -11021,7 +11340,7 @@ init_store_port();
11021
11340
  // src/cli/shared/enable-flow.ts
11022
11341
  init_esm_shims();
11023
11342
  init_flow_config();
11024
- function enableFlowConfig(store2) {
11343
+ function setFlowEnabled(store2, enabled) {
11025
11344
  const raw = store2.getProjectSetting(FLOW_CONFIG_SETTING_KEY);
11026
11345
  let current = {};
11027
11346
  if (raw) {
@@ -11031,24 +11350,88 @@ function enableFlowConfig(store2) {
11031
11350
  current = {};
11032
11351
  }
11033
11352
  }
11034
- store2.setProjectSetting(FLOW_CONFIG_SETTING_KEY, JSON.stringify({ ...current, enabled: true }));
11353
+ store2.setProjectSetting(FLOW_CONFIG_SETTING_KEY, JSON.stringify({ ...current, enabled }));
11354
+ }
11355
+ function enableFlowConfig(store2) {
11356
+ setFlowEnabled(store2, true);
11357
+ }
11358
+
11359
+ // src/cli/commands/profile-cmd.ts
11360
+ init_esm_shims();
11361
+
11362
+ // src/core/config/profiles.ts
11363
+ init_esm_shims();
11364
+ var BUILT_IN_PROFILES = {
11365
+ fast: { modelTier: "cheap", flow: false, retries: 1 },
11366
+ build: { modelTier: "build", flow: true, retries: 2 },
11367
+ frontier: { modelTier: "frontier", flow: true, retries: 3 }
11368
+ };
11369
+ function resolveProfile(name) {
11370
+ return BUILT_IN_PROFILES[name];
11371
+ }
11372
+ function listProfiles() {
11373
+ return Object.keys(BUILT_IN_PROFILES);
11035
11374
  }
11375
+
11376
+ // src/cli/commands/profile-cmd.ts
11377
+ init_tier_router();
11036
11378
  function output7(msg) {
11037
11379
  process.stdout.write(msg + "\n");
11038
11380
  }
11381
+ function applyProfile(store2, name) {
11382
+ const profile = resolveProfile(name);
11383
+ if (!profile) return void 0;
11384
+ store2.setProjectSetting("model", resolveTierModel(profile.modelTier));
11385
+ setFlowEnabled(store2, profile.flow);
11386
+ return profile;
11387
+ }
11388
+ function profileCommand() {
11389
+ const cmd = new Command("profile").description("Bundles de trabalho: tier de modelo + flow + retries");
11390
+ cmd.command("list").description("Lista os profiles dispon\xEDveis").action(() => {
11391
+ for (const name of listProfiles()) {
11392
+ const p = BUILT_IN_PROFILES[name];
11393
+ output7(`${name.padEnd(10)} tier=${p.modelTier} flow=${p.flow} retries=${p.retries}`);
11394
+ }
11395
+ });
11396
+ cmd.command("show <nome>").description("Detalha um profile").action((nome) => {
11397
+ const p = resolveProfile(nome);
11398
+ if (!p) {
11399
+ output7(`Profile desconhecido: ${nome}. Tente 'profile list'.`);
11400
+ process.exitCode = 1;
11401
+ return;
11402
+ }
11403
+ output7(`${nome}: tier=${p.modelTier} \xB7 flow=${p.flow} \xB7 retries=${p.retries} \xB7 modelo=${resolveTierModel(p.modelTier)}`);
11404
+ });
11405
+ return cmd;
11406
+ }
11407
+ function output8(msg) {
11408
+ process.stdout.write(msg + "\n");
11409
+ }
11039
11410
  function autopilotCommand() {
11040
- return new Command("autopilot").description("Loop aut\xF4nomo com guardrails: next \u2192 in_progress \u2192 DoD \u2192 done|escalate (WIP=1)").option("-d, --dir <dir>", "Diret\xF3rio do projeto", process.cwd()).option("-m, --max <n>", "Budget: m\xE1ximo de tasks por sess\xE3o (cost-runaway guard)", "5").option("--simulate", "Simula impl bem-sucedida (deixa o DoD real decidir) \u2014 n\xE3o escreve c\xF3digo", false).option("--live", "Invoca o modelo real via SDK do Copilot: gera plano \u2192 aplica \u2192 roda testes \u2192 done|escala", false).option("--test-cmd <cmd>", "Comando de teste rodado no modo --live quando o plano n\xE3o traz um", "npm test").option("--retries <n>", "Tentativas por task no --live (retry com feedback compacto do teste)", "2").option("--flow", "Ativa a dilui\xE7\xE3o de contexto por \u03BB_flow (hipofrontalidade) no --live", false).action(
11411
+ return new Command("autopilot").description("Loop aut\xF4nomo com guardrails: next \u2192 in_progress \u2192 DoD \u2192 done|escalate (WIP=1)").option("-d, --dir <dir>", "Diret\xF3rio do projeto", process.cwd()).option("-m, --max <n>", "Budget: m\xE1ximo de tasks por sess\xE3o (cost-runaway guard)", "5").option("--simulate", "Simula impl bem-sucedida (deixa o DoD real decidir) \u2014 n\xE3o escreve c\xF3digo", false).option("--live", "Invoca o modelo real via SDK do Copilot: gera plano \u2192 aplica \u2192 roda testes \u2192 done|escala", false).option("--test-cmd <cmd>", "Comando de teste rodado no modo --live quando o plano n\xE3o traz um", "npm test").option("--retries <n>", "Tentativas por task no --live (retry com feedback compacto do teste)", "2").option("--flow", "Ativa a dilui\xE7\xE3o de contexto por \u03BB_flow (hipofrontalidade) no --live", false).option("--profile <nome>", "Aplica um bundle de trabalho (fast|build|frontier): tier+flow+retries").action(
11041
11412
  async (opts) => {
11042
11413
  const store2 = openStoreOrFail(opts.dir, { requireExisting: true });
11043
11414
  try {
11044
11415
  const maxIterations = Math.max(1, parseInt(opts.max, 10) || 5);
11045
11416
  const port = makeStorePort(store2);
11417
+ let retriesOverride;
11418
+ if (opts.profile) {
11419
+ const applied = applyProfile(store2, opts.profile);
11420
+ if (!applied) {
11421
+ output8(`Profile desconhecido: ${opts.profile}. Tente 'profile list'.`);
11422
+ store2.close();
11423
+ return;
11424
+ }
11425
+ retriesOverride = applied.retries;
11426
+ output8(`[PROFILE] ${opts.profile}: tier=${applied.modelTier} flow=${applied.flow} retries=${applied.retries}
11427
+ `);
11428
+ }
11046
11429
  if (opts.flow) {
11047
11430
  enableFlowConfig(store2);
11048
- output7("[FLOW] \u03BB_flow ativo: contexto do grafo dilu\xEDdo por \u03A6(t) (esquecimento din\xE2mico).\n");
11431
+ output8("[FLOW] \u03BB_flow ativo: contexto do grafo dilu\xEDdo por \u03A6(t) (esquecimento din\xE2mico).\n");
11049
11432
  }
11050
- if (opts.simulate) output7("[SIMULA\xC7\xC3O] impl tratada como verde \u2014 DoD real decide prontid\xE3o.\n");
11051
- if (opts.live) output7("[LIVE] modelo via SDK do Copilot: gera plano \u2192 aplica no workspace \u2192 roda testes.\n");
11433
+ if (opts.simulate) output8("[SIMULA\xC7\xC3O] impl tratada como verde \u2014 DoD real decide prontid\xE3o.\n");
11434
+ if (opts.live) output8("[LIVE] modelo via SDK do Copilot: gera plano \u2192 aplica no workspace \u2192 roda testes.\n");
11052
11435
  let implement;
11053
11436
  let ledger;
11054
11437
  if (opts.live) {
@@ -11057,11 +11440,11 @@ function autopilotCommand() {
11057
11440
  store: store2,
11058
11441
  dir: opts.dir,
11059
11442
  testCmd: opts.testCmd,
11060
- retries: parseInt(opts.retries, 10) || 2,
11443
+ retries: retriesOverride ?? (parseInt(opts.retries, 10) || 2),
11061
11444
  ledger,
11062
- onLog: output7
11445
+ onLog: output8
11063
11446
  });
11064
- if (live.repoSymbolCount > 0) output7(`[LIVE] repo-map: ${live.repoSymbolCount} s\xEDmbolo(s) indexado(s).`);
11447
+ if (live.repoSymbolCount > 0) output8(`[LIVE] repo-map: ${live.repoSymbolCount} s\xEDmbolo(s) indexado(s).`);
11065
11448
  implement = live.implement;
11066
11449
  } else if (opts.simulate) {
11067
11450
  implement = () => true;
@@ -11071,28 +11454,28 @@ function autopilotCommand() {
11071
11454
  const result = await runAutopilot(port, { maxIterations, implement });
11072
11455
  for (const s of result.steps) {
11073
11456
  const icon = s.action === "done" ? "\u2713" : s.action === "escalated" ? "\u26A0" : "\u2192";
11074
- output7(`${icon} ${s.nodeId} ${s.title} [${s.action}] ${s.detail}`);
11457
+ output8(`${icon} ${s.nodeId} ${s.title} [${s.action}] ${s.detail}`);
11075
11458
  }
11076
- output7(`
11459
+ output8(`
11077
11460
  Resumo: ${result.completed} conclu\xEDda(s), ${result.escalated} escalada(s). Parou: ${result.stopped}`);
11078
11461
  if (ledger) {
11079
11462
  const totals = ledger.totals();
11080
- output7(`
11463
+ output8(`
11081
11464
  Tokens (sess\xE3o): ${totals.total} (in ${totals.tokensIn} / out ${totals.tokensOut}) em ${totals.calls} chamada(s)`);
11082
11465
  for (const t of ledger.tasks()) {
11083
- output7(` ${t.nodeId}: ${t.total} tok (in ${t.tokensIn} / out ${t.tokensOut}, ${t.calls} chamada(s))`);
11466
+ output8(` ${t.nodeId}: ${t.total} tok (in ${t.tokensIn} / out ${t.tokensOut}, ${t.calls} chamada(s))`);
11084
11467
  }
11085
11468
  if (result.completed > 0) {
11086
- output7(` m\xE9dia/task conclu\xEDda: ${Math.round(totals.total / result.completed)} tok`);
11469
+ output8(` m\xE9dia/task conclu\xEDda: ${Math.round(totals.total / result.completed)} tok`);
11087
11470
  }
11088
11471
  if (totals.calls > 0) {
11089
11472
  const sessionId = `autopilot_${randomUUID().replace(/-/g, "").slice(0, 12)}`;
11090
11473
  const rows = persistLedger(store2.getDb(), ledger, { sessionId, provider: "copilot" });
11091
- output7(` ${rows} chamada(s) persistida(s) (session ${sessionId})`);
11474
+ output8(` ${rows} chamada(s) persistida(s) (session ${sessionId})`);
11092
11475
  }
11093
11476
  }
11094
11477
  if (!opts.simulate && !opts.live && result.stopped === "escalation") {
11095
- output7("\nDica: --simulate exercita o loop + gate DoD; --live invoca o modelo real via SDK do Copilot.");
11478
+ output8("\nDica: --simulate exercita o loop + gate DoD; --live invoca o modelo real via SDK do Copilot.");
11096
11479
  }
11097
11480
  } finally {
11098
11481
  store2.close();
@@ -11105,7 +11488,7 @@ Tokens (sess\xE3o): ${totals.total} (in ${totals.tokensIn} / out ${totals.tokens
11105
11488
  init_esm_shims();
11106
11489
  init_tier_router();
11107
11490
  var SETTING_KEY = "model";
11108
- function output8(msg) {
11491
+ function output9(msg) {
11109
11492
  process.stdout.write(msg + "\n");
11110
11493
  }
11111
11494
  function readConfig(dir) {
@@ -11122,29 +11505,29 @@ function modelCommand() {
11122
11505
  "Seleciona/inspeciona o modelo do tier-router (pool do Copilot CLI; 'auto' roteia por tarefa)"
11123
11506
  );
11124
11507
  cmd.command("list").description("Lista o pool agrupado por tier").action(() => {
11125
- output8("auto \u2014 roteia por tarefa (cheap classifica \xB7 build implementa \xB7 frontier planeja)\n");
11508
+ output9("auto \u2014 roteia por tarefa (cheap classifica \xB7 build implementa \xB7 frontier planeja)\n");
11126
11509
  for (const tier of MODEL_TIERS) {
11127
- output8(`[${tier}]`);
11510
+ output9(`[${tier}]`);
11128
11511
  for (const m of modelsForTier(tier)) {
11129
11512
  const mark = m.id === DEFAULT_MODEL ? " (default)" : "";
11130
- output8(` ${m.id} \u2014 ${m.label}${mark}`);
11513
+ output9(` ${m.id} \u2014 ${m.label}${mark}`);
11131
11514
  }
11132
11515
  }
11133
11516
  });
11134
11517
  cmd.command("current").description("Mostra o modelo/modo selecionado").option("-d, --dir <dir>", "Diret\xF3rio do projeto", process.cwd()).action((opts) => {
11135
11518
  const config = readConfig(opts.dir);
11136
- output8(config.mode === "auto" ? "auto (roteamento por tarefa)" : `pinned: ${config.modelId}`);
11519
+ output9(config.mode === "auto" ? "auto (roteamento por tarefa)" : `pinned: ${config.modelId}`);
11137
11520
  });
11138
11521
  cmd.command("set").description("Fixa um modelo (id do pool) ou 'auto' para roteamento por tarefa").argument("<idOrAuto>", "ID do modelo ou 'auto'").option("-d, --dir <dir>", "Diret\xF3rio do projeto", process.cwd()).action((idOrAuto, opts) => {
11139
11522
  const value = idOrAuto.trim();
11140
11523
  if (value !== "auto" && !isKnownModel(value)) {
11141
- output8(`Modelo desconhecido: "${value}". Rode 'model list' para ver o pool.`);
11524
+ output9(`Modelo desconhecido: "${value}". Rode 'model list' para ver o pool.`);
11142
11525
  process.exit(1);
11143
11526
  }
11144
11527
  const store2 = openStoreOrFail(opts.dir, { requireExisting: true });
11145
11528
  try {
11146
11529
  store2.setProjectSetting(SETTING_KEY, value);
11147
- output8(value === "auto" ? "Modo: auto (roteamento por tarefa)." : `Modelo fixado: ${value}.`);
11530
+ output9(value === "auto" ? "Modo: auto (roteamento por tarefa)." : `Modelo fixado: ${value}.`);
11148
11531
  } finally {
11149
11532
  store2.close();
11150
11533
  }
@@ -11152,11 +11535,11 @@ function modelCommand() {
11152
11535
  cmd.command("route").description("Mostra qual modelo o router escolhe para um tipo de tarefa").argument("<kind>", `Tipo: ${TaskKindSchema.options.join("|")}`).option("-d, --dir <dir>", "Diret\xF3rio do projeto", process.cwd()).action((kind, opts) => {
11153
11536
  const parsed = TaskKindSchema.safeParse(kind);
11154
11537
  if (!parsed.success) {
11155
- output8(`Tipo inv\xE1lido: "${kind}". Esperado: ${TaskKindSchema.options.join(", ")}.`);
11538
+ output9(`Tipo inv\xE1lido: "${kind}". Esperado: ${TaskKindSchema.options.join(", ")}.`);
11156
11539
  process.exit(1);
11157
11540
  }
11158
11541
  const config = readConfig(opts.dir);
11159
- output8(`${kind} \u2192 ${routeModel(config, parsed.data)}`);
11542
+ output9(`${kind} \u2192 ${routeModel(config, parsed.data)}`);
11160
11543
  });
11161
11544
  return cmd;
11162
11545
  }
@@ -11164,7 +11547,7 @@ function modelCommand() {
11164
11547
  // src/cli/commands/metrics-cmd.ts
11165
11548
  init_esm_shims();
11166
11549
  init_llm_call_ledger();
11167
- function output9(msg) {
11550
+ function output10(msg) {
11168
11551
  process.stdout.write(msg + "\n");
11169
11552
  }
11170
11553
  function metricsCommand() {
@@ -11173,30 +11556,30 @@ function metricsCommand() {
11173
11556
  try {
11174
11557
  const summary = summarizeLedger(store2.getDb(), { sessionId: opts.session });
11175
11558
  if (summary.totals.calls === 0) {
11176
- output9("Sem chamadas de modelo registradas. Rode `autopilot --live` para gerar m\xE9tricas.");
11559
+ output10("Sem chamadas de modelo registradas. Rode `autopilot --live` para gerar m\xE9tricas.");
11177
11560
  return;
11178
11561
  }
11179
11562
  const usd = (n) => `$${n.toFixed(4)}`;
11180
11563
  const { totals } = summary;
11181
11564
  const taskCount = summary.byTask.length;
11182
11565
  const avgCost = taskCount > 0 ? totals.costUsd / taskCount : 0;
11183
- output9(`Tokens totais: ${totals.total} (in ${totals.tokensIn} / out ${totals.tokensOut}) em ${totals.calls} chamada(s)`);
11184
- output9(`Custo total: \u2248 ${usd(totals.costUsd)}`);
11185
- output9(`M\xE9dia por task: ${summary.avgTokensPerTask} tok \u2248 ${usd(avgCost)} | ${taskCount} task(s), ${summary.bySession.length} sess\xE3o(\xF5es)`);
11566
+ output10(`Tokens totais: ${totals.total} (in ${totals.tokensIn} / out ${totals.tokensOut}) em ${totals.calls} chamada(s)`);
11567
+ output10(`Custo total: \u2248 ${usd(totals.costUsd)}`);
11568
+ output10(`M\xE9dia por task: ${summary.avgTokensPerTask} tok \u2248 ${usd(avgCost)} | ${taskCount} task(s), ${summary.bySession.length} sess\xE3o(\xF5es)`);
11186
11569
  const top = Math.max(1, parseInt(opts.top, 10) || 10);
11187
- output9(`
11570
+ output10(`
11188
11571
  Tokens por task (top ${top}):`);
11189
11572
  for (const t of summary.byTask.slice(0, top)) {
11190
- output9(` ${t.nodeId}: ${t.total} tok \u2248 ${usd(t.costUsd)} (in ${t.tokensIn} / out ${t.tokensOut}, ${t.calls} chamada(s))`);
11573
+ output10(` ${t.nodeId}: ${t.total} tok \u2248 ${usd(t.costUsd)} (in ${t.tokensIn} / out ${t.tokensOut}, ${t.calls} chamada(s))`);
11191
11574
  }
11192
11575
  if (!opts.session) {
11193
- output9("\nTokens por sess\xE3o:");
11576
+ output10("\nTokens por sess\xE3o:");
11194
11577
  for (const s of summary.bySession) {
11195
- output9(` ${s.sessionId}: ${s.total} tok \u2248 ${usd(s.costUsd)} (${s.calls} chamada(s))`);
11578
+ output10(` ${s.sessionId}: ${s.total} tok \u2248 ${usd(s.costUsd)} (${s.calls} chamada(s))`);
11196
11579
  }
11197
11580
  }
11198
11581
  if (totals.costUsd === 0) {
11199
- output9("\nNota: custo $0 \u2014 modelos usados n\xE3o t\xEAm pre\xE7o cadastrado (ver cost-tracker MODEL_PRICING).");
11582
+ output10("\nNota: custo $0 \u2014 modelos usados n\xE3o t\xEAm pre\xE7o cadastrado (ver cost-tracker MODEL_PRICING).");
11200
11583
  }
11201
11584
  } finally {
11202
11585
  store2.close();
@@ -11211,7 +11594,7 @@ init_resolve_adapter();
11211
11594
  init_implementation_executor();
11212
11595
  init_implement_attempt();
11213
11596
  init_token_ledger();
11214
- function output10(msg) {
11597
+ function output11(msg) {
11215
11598
  process.stdout.write(msg + "\n");
11216
11599
  }
11217
11600
  function runCommand() {
@@ -11222,7 +11605,7 @@ function runCommand() {
11222
11605
  const maxAttempts = Math.max(1, parseInt(opts.retries, 10) || 2);
11223
11606
  const ledger = new TokenLedger();
11224
11607
  const node = { id: `run_${randomUUID().replace(/-/g, "").slice(0, 8)}`, title: prompt };
11225
- output10(`[run] ${client.modelFor("implement")} via ${resolved.kind === "api" ? "API HTTP" : "CLI"} \u2192 "${prompt}"`);
11608
+ output11(`[run] ${client.modelFor("implement")} via ${resolved.kind === "api" ? "API HTTP" : "CLI"} \u2192 "${prompt}"`);
11226
11609
  const outcome = await attemptImplementation(
11227
11610
  {
11228
11611
  generate: async (p) => {
@@ -11242,7 +11625,7 @@ function runCommand() {
11242
11625
  );
11243
11626
  const files = outcome.lastResult?.applied.length ?? 0;
11244
11627
  const totals = ledger.totals();
11245
- output10(
11628
+ output11(
11246
11629
  `${outcome.success ? "\u2713" : "\u26A0"} ${outcome.attempts} tentativa(s), ${files} arquivo(s), ${totals.total} tok (in ${totals.tokensIn} / out ${totals.tokensOut}) \u2192 ${outcome.success ? "verde" : "falhou (testes n\xE3o passaram)"}`
11247
11630
  );
11248
11631
  if (!outcome.success) process.exitCode = 1;
@@ -11266,7 +11649,7 @@ function tuiCommand() {
11266
11649
  // src/cli/commands/login-cmd.ts
11267
11650
  init_esm_shims();
11268
11651
  init_copilot_auth();
11269
- function output11(msg) {
11652
+ function output12(msg) {
11270
11653
  process.stdout.write(msg + "\n");
11271
11654
  }
11272
11655
  function loginCommand() {
@@ -11274,15 +11657,15 @@ function loginCommand() {
11274
11657
  const path22 = defaultAuthPath();
11275
11658
  if (opts.token) {
11276
11659
  saveAuth(path22, { githubToken: opts.token });
11277
- output11(`\u2713 Token salvo em ${path22}. Provider HTTP do Copilot habilitado.`);
11660
+ output12(`\u2713 Token salvo em ${path22}. Provider HTTP do Copilot habilitado.`);
11278
11661
  return;
11279
11662
  }
11280
11663
  const device = await requestDeviceCode(globalThis.fetch);
11281
- output11("\nPara autenticar, abra:");
11282
- output11(` ${device.verificationUri}`);
11283
- output11(`e informe o c\xF3digo: \x1B[1m${device.userCode}\x1B[0m
11664
+ output12("\nPara autenticar, abra:");
11665
+ output12(` ${device.verificationUri}`);
11666
+ output12(`e informe o c\xF3digo: \x1B[1m${device.userCode}\x1B[0m
11284
11667
  `);
11285
- output11("Aguardando autoriza\xE7\xE3o\u2026 (Ctrl+C para cancelar)");
11668
+ output12("Aguardando autoriza\xE7\xE3o\u2026 (Ctrl+C para cancelar)");
11286
11669
  const deadline = Date.now() + device.expiresIn * 1e3;
11287
11670
  let interval = Math.max(1, device.interval);
11288
11671
  while (Date.now() < deadline) {
@@ -11290,13 +11673,13 @@ function loginCommand() {
11290
11673
  const r = await pollForAccessToken(globalThis.fetch, device.deviceCode);
11291
11674
  if ("accessToken" in r) {
11292
11675
  saveAuth(path22, { githubToken: r.accessToken });
11293
- output11(`
11676
+ output12(`
11294
11677
  \u2713 Autenticado. Token salvo em ${path22}. Provider HTTP do Copilot habilitado.`);
11295
11678
  return;
11296
11679
  }
11297
11680
  if ("slowDown" in r) interval += 5;
11298
11681
  }
11299
- output11("\n\u26A0 Tempo esgotado sem autoriza\xE7\xE3o. Rode `agf login` de novo.");
11682
+ output12("\n\u26A0 Tempo esgotado sem autoriza\xE7\xE3o. Rode `agf login` de novo.");
11300
11683
  process.exitCode = 1;
11301
11684
  });
11302
11685
  }
@@ -11305,9 +11688,9 @@ function logoutCommand() {
11305
11688
  const path22 = defaultAuthPath();
11306
11689
  if (existsSync(path22) && loadAuth(path22)) {
11307
11690
  rmSync(path22, { force: true });
11308
- output11(`\u2713 Logout \u2014 ${path22} removido.`);
11691
+ output12(`\u2713 Logout \u2014 ${path22} removido.`);
11309
11692
  } else {
11310
- output11("Nenhum login salvo.");
11693
+ output12("Nenhum login salvo.");
11311
11694
  }
11312
11695
  });
11313
11696
  }
@@ -13968,7 +14351,7 @@ function reapDaemons(options = {}) {
13968
14351
  // src/cli/commands/daemon-cmd.ts
13969
14352
  init_logger();
13970
14353
  var log44 = createLogger({ layer: "cli", source: "daemon.ts" });
13971
- function output12(msg) {
14354
+ function output13(msg) {
13972
14355
  process.stdout.write(msg + "\n");
13973
14356
  }
13974
14357
  function daemonCommand() {
@@ -13979,23 +14362,23 @@ function daemonCommand() {
13979
14362
  const prefix = opts.dryRun ? "[dry-run] " : "";
13980
14363
  for (const a of report.actions) {
13981
14364
  if (a.outcome === "kept") continue;
13982
- output12(`${prefix}${a.outcome}: ${a.stateDir}${a.pid ? ` (pid=${a.pid})` : ""} \u2014 ${a.reason}`);
14365
+ output13(`${prefix}${a.outcome}: ${a.stateDir}${a.pid ? ` (pid=${a.pid})` : ""} \u2014 ${a.reason}`);
13983
14366
  }
13984
- output12(
14367
+ output13(
13985
14368
  `${prefix}daemon prune: scanned ${report.scanned}, killed ${report.killed}, removed ${report.removed}, kept ${report.kept}`
13986
14369
  );
13987
14370
  });
13988
14371
  cmd.command("list").description("List daemon state directories and their status (read-only)").action(() => {
13989
14372
  const report = reapDaemons({ dryRun: true });
13990
14373
  if (report.scanned === 0) {
13991
- output12("daemon list: no daemon state directories found");
14374
+ output13("daemon list: no daemon state directories found");
13992
14375
  return;
13993
14376
  }
13994
14377
  for (const a of report.actions) {
13995
14378
  const would = a.outcome === "kept" ? "alive" : `stale \u2192 would ${a.outcome}`;
13996
- output12(`${would}: ${a.stateDir}${a.pid ? ` (pid=${a.pid})` : ""} \u2014 ${a.reason}`);
14379
+ output13(`${would}: ${a.stateDir}${a.pid ? ` (pid=${a.pid})` : ""} \u2014 ${a.reason}`);
13997
14380
  }
13998
- output12(`daemon list: ${report.scanned} state dir(s), ${report.kept} alive`);
14381
+ output13(`daemon list: ${report.scanned} state dir(s), ${report.kept} alive`);
13999
14382
  });
14000
14383
  return cmd;
14001
14384
  }
@@ -14047,7 +14430,7 @@ function formatProviderReport(report) {
14047
14430
  init_errors();
14048
14431
  init_logger();
14049
14432
  var log45 = createLogger({ layer: "cli", source: "doctor.ts" });
14050
- function output13(msg) {
14433
+ function output14(msg) {
14051
14434
  process.stdout.write(msg + "\n");
14052
14435
  }
14053
14436
  var LEVEL_ICON2 = {
@@ -14070,31 +14453,31 @@ function doctorCommand() {
14070
14453
  if (opts.providers) {
14071
14454
  const providerReport = checkProviders();
14072
14455
  if (opts.json) {
14073
- output13(JSON.stringify(providerReport, null, 2));
14456
+ output14(JSON.stringify(providerReport, null, 2));
14074
14457
  } else {
14075
- output13("mcp-graph doctor \u2014 LLM providers\n");
14458
+ output14("mcp-graph doctor \u2014 LLM providers\n");
14076
14459
  for (const line of formatProviderReport(providerReport)) {
14077
- output13(line);
14460
+ output14(line);
14078
14461
  }
14079
14462
  }
14080
14463
  return;
14081
14464
  }
14082
14465
  const report = await runDoctor(opts.dir);
14083
14466
  if (opts.json) {
14084
- output13(JSON.stringify(report, null, 2));
14467
+ output14(JSON.stringify(report, null, 2));
14085
14468
  } else {
14086
- output13("mcp-graph doctor\n");
14469
+ output14("mcp-graph doctor\n");
14087
14470
  for (const check of report.checks) {
14088
- output13(formatCheck(check));
14471
+ output14(formatCheck(check));
14089
14472
  }
14090
- output13("");
14091
- output13(
14473
+ output14("");
14474
+ output14(
14092
14475
  `Summary: ${report.summary.ok} ok, ${report.summary.warning} warnings, ${report.summary.error} errors`
14093
14476
  );
14094
14477
  if (report.passed) {
14095
- output13("\nAll critical checks passed.");
14478
+ output14("\nAll critical checks passed.");
14096
14479
  } else {
14097
- output13("\nSome critical checks failed. Fix errors above.");
14480
+ output14("\nSome critical checks failed. Fix errors above.");
14098
14481
  }
14099
14482
  }
14100
14483
  if (!report.passed) {
@@ -14178,13 +14561,13 @@ function pruneOrphanWorktrees(options) {
14178
14561
  }
14179
14562
  }
14180
14563
  try {
14181
- const output16 = execSync("git worktree prune --verbose", execOpts).toString();
14564
+ const output17 = execSync("git worktree prune --verbose", execOpts).toString();
14182
14565
  if (reapedBranches > 0 || reapedWorktrees > 0) {
14183
14566
  log46.info("shadow-branch:prune-ok", { reapedBranches, reapedWorktrees, ttlMs });
14184
14567
  } else {
14185
- log46.debug("shadow-branch:prune-ok", { reapedBranches, reapedWorktrees, output: output16 });
14568
+ log46.debug("shadow-branch:prune-ok", { reapedBranches, reapedWorktrees, output: output17 });
14186
14569
  }
14187
- return { pruned: true, reapedBranches, reapedWorktrees, output: output16 };
14570
+ return { pruned: true, reapedBranches, reapedWorktrees, output: output17 };
14188
14571
  } catch (err) {
14189
14572
  const error = String(err);
14190
14573
  log46.debug("shadow-branch:prune-failed", { error });
@@ -14195,7 +14578,7 @@ function pruneOrphanWorktrees(options) {
14195
14578
  // src/cli/commands/gc-cmd.ts
14196
14579
  init_logger();
14197
14580
  var log47 = createLogger({ layer: "cli", source: "gc.ts" });
14198
- function output14(msg) {
14581
+ function output15(msg) {
14199
14582
  process.stdout.write(msg + "\n");
14200
14583
  }
14201
14584
  function gcCommand() {
@@ -14205,9 +14588,9 @@ function gcCommand() {
14205
14588
  log47.info("cli:gc:start", { dir: opts.dir, ttlMs });
14206
14589
  const result = pruneOrphanWorktrees({ cwd: opts.dir, ttlMs });
14207
14590
  if (result.pruned) {
14208
- output14(`gc: reaped ${result.reapedBranches} branches, ${result.reapedWorktrees} worktrees`);
14591
+ output15(`gc: reaped ${result.reapedBranches} branches, ${result.reapedWorktrees} worktrees`);
14209
14592
  } else {
14210
- output14(`gc: failed \u2014 ${result.error ?? "unknown error"}`);
14593
+ output15(`gc: failed \u2014 ${result.error ?? "unknown error"}`);
14211
14594
  process.exit(1);
14212
14595
  }
14213
14596
  });
@@ -14216,7 +14599,7 @@ function gcCommand() {
14216
14599
  // src/cli/commands/skill-cmd.ts
14217
14600
  init_esm_shims();
14218
14601
  init_skill_registry();
14219
- function output15(msg) {
14602
+ function output16(msg) {
14220
14603
  process.stdout.write(msg + "\n");
14221
14604
  }
14222
14605
  function skillCommand() {
@@ -14230,26 +14613,26 @@ function skillCommand() {
14230
14613
  if (seen.has(s.name)) continue;
14231
14614
  seen.add(s.name);
14232
14615
  count += 1;
14233
- output15(`${s.name.padEnd(28)} [${s.category}] ${s.description}`);
14616
+ output16(`${s.name.padEnd(28)} [${s.category}] ${s.description}`);
14234
14617
  }
14235
14618
  }
14236
- if (count === 0) output15("Nenhuma skill encontrada.");
14237
- else output15(`
14619
+ if (count === 0) output16("Nenhuma skill encontrada.");
14620
+ else output16(`
14238
14621
  ${count} skill(s).`);
14239
14622
  });
14240
14623
  cmd.command("show <nome>").description("Imprime as instru\xE7\xF5es completas de uma skill").option("-d, --dir <dir>", "Raiz do projeto", process.cwd()).action((nome, opts) => {
14241
14624
  for (const root of defaultSkillRoots(opts.dir)) {
14242
14625
  const found = invokeSkill(root, nome);
14243
14626
  if (found) {
14244
- output15(`=== ${found.name} ===`);
14245
- output15(`[${found.category}] ${found.description}`);
14246
- if (found.phases.length > 0) output15(`fases: ${found.phases.join(", ")}`);
14247
- output15("");
14248
- output15(found.body);
14627
+ output16(`=== ${found.name} ===`);
14628
+ output16(`[${found.category}] ${found.description}`);
14629
+ if (found.phases.length > 0) output16(`fases: ${found.phases.join(", ")}`);
14630
+ output16("");
14631
+ output16(found.body);
14249
14632
  return;
14250
14633
  }
14251
14634
  }
14252
- output15(`Skill n\xE3o encontrada: ${nome}. Tente 'skill list'.`);
14635
+ output16(`Skill n\xE3o encontrada: ${nome}. Tente 'skill list'.`);
14253
14636
  process.exitCode = 1;
14254
14637
  });
14255
14638
  return cmd;
@@ -14276,6 +14659,7 @@ program.addCommand(daemonCommand());
14276
14659
  program.addCommand(doctorCommand());
14277
14660
  program.addCommand(gcCommand());
14278
14661
  program.addCommand(skillCommand());
14662
+ program.addCommand(profileCommand());
14279
14663
  function shouldLaunchTui() {
14280
14664
  const noArgs = process.argv.length <= 2;
14281
14665
  const isTty = Boolean(process.stdin.isTTY) && Boolean(process.stdout.isTTY);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mcp-graph-workflow/agent-graph-flow",
3
- "version": "0.2.0",
3
+ "version": "0.4.0",
4
4
  "description": "Agente SWE autônomo, local-first e token-frugal: PRD → grafo de execução persistente, TDD obrigatório, custo de token brutalmente baixo. AGPL v3.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",