@mcp-graph-workflow/agent-graph-flow 0.2.0 → 0.3.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 +205 -9
  2. package/package.json +1 -1
package/dist/cli/index.js CHANGED
@@ -11,7 +11,7 @@ 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
16
  import { createElement, useState, useCallback, useEffect } from 'react';
17
17
  import TextInput from 'ink-text-input';
@@ -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;
@@ -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,16 +7280,40 @@ 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
  }
@@ -7219,8 +7332,10 @@ var init_implement_attempt = __esm({
7219
7332
  "src/core/autonomy/implement-attempt.ts"() {
7220
7333
  init_esm_shims();
7221
7334
  init_logger();
7335
+ init_errors();
7222
7336
  init_truncate();
7223
7337
  init_plan_parser();
7338
+ init_llm_error();
7224
7339
  log25 = createLogger({ layer: "core", source: "implement-attempt.ts" });
7225
7340
  DEFAULT_MAX_FEEDBACK_CHARS = 1200;
7226
7341
  }
@@ -8568,6 +8683,18 @@ var init_store_port = __esm({
8568
8683
  init_definition_of_done();
8569
8684
  }
8570
8685
  });
8686
+
8687
+ // src/tui/status-line.ts
8688
+ function formatStatusLine(input) {
8689
+ const tokens = `${Math.max(0, Math.round(input.totalTokens))} tok`;
8690
+ const cost = `$${input.costUsd.toFixed(4)}`;
8691
+ return `\u26C1 ${tokens} \xB7 ${cost} \xB7 ${input.model}`;
8692
+ }
8693
+ var init_status_line = __esm({
8694
+ "src/tui/status-line.ts"() {
8695
+ init_esm_shims();
8696
+ }
8697
+ });
8571
8698
  function TaskRow({ task }) {
8572
8699
  return /* @__PURE__ */ jsxs(Text, { children: [
8573
8700
  " ",
@@ -8594,6 +8721,7 @@ function App({ model }) {
8594
8721
  " ",
8595
8722
  model.wip
8596
8723
  ] }) }),
8724
+ /* @__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
8725
  /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginTop: 1, children: [
8598
8726
  /* @__PURE__ */ jsxs(Text, { bold: true, children: [
8599
8727
  "Tasks ativas (",
@@ -8624,6 +8752,7 @@ var STATUS_ICON;
8624
8752
  var init_app = __esm({
8625
8753
  "src/tui/app.tsx"() {
8626
8754
  init_esm_shims();
8755
+ init_status_line();
8627
8756
  STATUS_ICON = {
8628
8757
  in_progress: "\u25CF",
8629
8758
  ready: "\u25CB",
@@ -8674,6 +8803,7 @@ var init_banner_screen = __esm({
8674
8803
  function CommandBar({ value, onChange, onSubmit, suggestions }) {
8675
8804
  return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
8676
8805
  suggestions.length > 0 && /* @__PURE__ */ jsx(Box, { flexDirection: "column", marginBottom: 1, children: suggestions.map((c) => /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
8806
+ c.source === "skill" ? /* @__PURE__ */ jsx(Text, { color: "magenta", children: "[skill] " }) : null,
8677
8807
  c.usage,
8678
8808
  " \u2014 ",
8679
8809
  c.desc
@@ -8690,6 +8820,28 @@ var init_command_bar = __esm({
8690
8820
  }
8691
8821
  });
8692
8822
 
8823
+ // src/tui/history.ts
8824
+ function valueAt(state, cursor) {
8825
+ if (cursor < 0) return state.draft;
8826
+ return state.history[state.history.length - 1 - cursor];
8827
+ }
8828
+ function navigateHistory(state, dir) {
8829
+ const last = state.history.length - 1;
8830
+ if (last < 0) return { value: state.draft, cursor: -1 };
8831
+ let cursor = state.cursor;
8832
+ if (dir === "up") {
8833
+ cursor = Math.min(cursor + 1, last);
8834
+ } else {
8835
+ cursor = Math.max(cursor - 1, -1);
8836
+ }
8837
+ return { value: valueAt(state, cursor), cursor };
8838
+ }
8839
+ var init_history = __esm({
8840
+ "src/tui/history.ts"() {
8841
+ init_esm_shims();
8842
+ }
8843
+ });
8844
+
8693
8845
  // src/tui/dispatch.ts
8694
8846
  function parseCommand(input) {
8695
8847
  const trimmed = input.trim();
@@ -8699,11 +8851,38 @@ function parseCommand(input) {
8699
8851
  if (sp === -1) return { cmd: body.toLowerCase(), args: "" };
8700
8852
  return { cmd: body.slice(0, sp).toLowerCase(), args: body.slice(sp + 1).trim() };
8701
8853
  }
8854
+ function fuzzyScore(query, text) {
8855
+ const q = query.toLowerCase();
8856
+ const t = text.toLowerCase();
8857
+ if (q === "") return 0;
8858
+ let qi = 0;
8859
+ let score = 0;
8860
+ let lastMatch = -1;
8861
+ for (let ti = 0; ti < t.length && qi < q.length; ti++) {
8862
+ if (t[ti] === q[qi]) {
8863
+ if (lastMatch === -1) score += ti;
8864
+ else score += ti - lastMatch - 1;
8865
+ lastMatch = ti;
8866
+ qi++;
8867
+ }
8868
+ }
8869
+ return qi === q.length ? score : null;
8870
+ }
8871
+ function fuzzyFilter(query, commands) {
8872
+ if (query.trim() === "") return [...commands];
8873
+ const scored = [];
8874
+ commands.forEach((cmd, idx) => {
8875
+ const score = fuzzyScore(query, cmd.name);
8876
+ if (score !== null) scored.push({ cmd, score, idx });
8877
+ });
8878
+ scored.sort((a, b) => a.score !== b.score ? a.score - b.score : a.idx - b.idx);
8879
+ return scored.map((s) => s.cmd);
8880
+ }
8702
8881
  function filterCommands(input, extra = []) {
8703
8882
  const trimmed = input.trim();
8704
8883
  if (!trimmed.startsWith("/")) return [];
8705
- const prefix = trimmed.slice(1).split(" ")[0].toLowerCase();
8706
- return [...COMMANDS, ...extra].filter((c) => c.name.startsWith(prefix));
8884
+ const query = trimmed.slice(1).split(" ")[0];
8885
+ return fuzzyFilter(query, [...COMMANDS, ...extra]);
8707
8886
  }
8708
8887
  async function runAsyncCommand(port, parsed, _onLine) {
8709
8888
  switch (parsed.cmd) {
@@ -8803,12 +8982,27 @@ function InteractiveApp({ dashboard, port, asyncPort, liveRunner, skillCommands
8803
8982
  const [log48, setLog] = useState([]);
8804
8983
  const [running, setRunning] = useState(false);
8805
8984
  const [showHelp, setShowHelp] = useState(false);
8985
+ const [history, setHistory] = useState([]);
8986
+ const [histCursor, setHistCursor] = useState(-1);
8987
+ const [draft, setDraft] = useState("");
8806
8988
  const append = (line) => setLog((prev) => [...prev, line].slice(-MAX_LOG_LINES));
8989
+ useInput((_input, key) => {
8990
+ if (phase !== "dashboard" || running) return;
8991
+ if (!key.upArrow && !key.downArrow) return;
8992
+ const effectiveDraft = histCursor === -1 ? input : draft;
8993
+ if (histCursor === -1) setDraft(input);
8994
+ const result = navigateHistory({ history, cursor: histCursor, draft: effectiveDraft }, key.upArrow ? "up" : "down");
8995
+ setHistCursor(result.cursor);
8996
+ setInput(result.value);
8997
+ });
8807
8998
  const submit = (raw) => {
8808
8999
  const parsed = parseCommand(raw);
8809
9000
  setInput("");
8810
9001
  const text = raw.trim();
8811
9002
  if (text === "" || running) return;
9003
+ setHistory((prev) => [...prev, text]);
9004
+ setHistCursor(-1);
9005
+ setDraft("");
8812
9006
  if (parsed.cmd === "quit") {
8813
9007
  exit();
8814
9008
  return;
@@ -8872,6 +9066,7 @@ var init_interactive_app = __esm({
8872
9066
  init_app();
8873
9067
  init_banner_screen();
8874
9068
  init_command_bar();
9069
+ init_history();
8875
9070
  init_dispatch();
8876
9071
  MAX_LOG_LINES = 12;
8877
9072
  }
@@ -10403,7 +10598,8 @@ async function launchTui(store2) {
10403
10598
  return skills.map((s) => ({
10404
10599
  name: s.name,
10405
10600
  usage: `/${s.name}`,
10406
- desc: `[${s.category}] ${s.description}`
10601
+ desc: `[${s.category}] ${s.description}`,
10602
+ source: "skill"
10407
10603
  }));
10408
10604
  });
10409
10605
  const instance = render(
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.3.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",