pudu-ai 0.2.22 → 0.2.23
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/cli/chunk-473MNTMS.js +24 -0
- package/dist/cli/chunk-47LK5I2X.js +39 -0
- package/dist/cli/{chunk-6NXOK4OD.js → chunk-4VEDGI4L.js} +7 -23
- package/dist/cli/{chunk-PX7BJDD3.js → chunk-6BGYI7XY.js} +7 -38
- package/dist/cli/{chunk-LL5FQT7I.js → chunk-7KPNGK6A.js} +2 -19
- package/dist/cli/chunk-XEEGLMVT.js +25 -0
- package/dist/cli/harness-launch-KEUKZZXH.js +118 -0
- package/dist/cli/index.js +13 -5
- package/dist/cli/{render-SKZLGUOW.js → render-Y4DNH7FW.js} +75 -20
- package/dist/cli/{repo-cli-TPYCTV5J.js → repo-cli-BK76EVBZ.js} +9 -5
- package/package.json +1 -1
- package/python/pudu_agent/__main__.py +7 -1
- package/python/pudu_agent/__pycache__/__init__.cpython-312.pyc +0 -0
- package/python/pudu_agent/__pycache__/__main__.cpython-312.pyc +0 -0
- package/python/pudu_agent/__pycache__/ast_search.cpython-312.pyc +0 -0
- package/python/pudu_agent/__pycache__/graph.cpython-312.pyc +0 -0
- package/python/pudu_agent/__pycache__/protocol.cpython-312.pyc +0 -0
- package/python/pudu_agent/__pycache__/search.cpython-312.pyc +0 -0
- package/python/pudu_agent/mcp_server.py +140 -0
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// src/shared/format.ts
|
|
2
|
+
function formatNumber(value, digits = 1) {
|
|
3
|
+
if (value === void 0 || Number.isNaN(value)) return "N/A";
|
|
4
|
+
return value.toFixed(digits);
|
|
5
|
+
}
|
|
6
|
+
function formatPercent(value, digits = 0) {
|
|
7
|
+
if (value === void 0 || Number.isNaN(value)) return "N/A";
|
|
8
|
+
return `${value.toFixed(digits)} %`;
|
|
9
|
+
}
|
|
10
|
+
function formatTokensPerSec(value, digits = 1) {
|
|
11
|
+
if (value === void 0 || Number.isNaN(value)) return "N/A";
|
|
12
|
+
return `${value.toFixed(digits)} t/s`;
|
|
13
|
+
}
|
|
14
|
+
function na(value) {
|
|
15
|
+
if (value === void 0 || value === null || value === "") return "N/A";
|
|
16
|
+
return String(value);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export {
|
|
20
|
+
formatNumber,
|
|
21
|
+
formatPercent,
|
|
22
|
+
formatTokensPerSec,
|
|
23
|
+
na
|
|
24
|
+
};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// src/integrations/catalog.ts
|
|
2
|
+
var INTEGRATIONS = {
|
|
3
|
+
opencode: {
|
|
4
|
+
id: "opencode",
|
|
5
|
+
docsUrl: "https://docs.ollama.com/integrations/opencode",
|
|
6
|
+
ollamaLaunch: "opencode",
|
|
7
|
+
minGenerationTps: 12,
|
|
8
|
+
allowedGrades: ["S", "A", "B"],
|
|
9
|
+
useCases: ["code", "chat", "reasoning"]
|
|
10
|
+
},
|
|
11
|
+
openclaw: {
|
|
12
|
+
id: "openclaw",
|
|
13
|
+
docsUrl: "https://docs.ollama.com/integrations/openclaw",
|
|
14
|
+
ollamaLaunch: "openclaw",
|
|
15
|
+
minGenerationTps: 12,
|
|
16
|
+
allowedGrades: ["S", "A", "B"],
|
|
17
|
+
useCases: ["code", "chat", "reasoning"]
|
|
18
|
+
},
|
|
19
|
+
hermes: {
|
|
20
|
+
id: "hermes",
|
|
21
|
+
docsUrl: "https://docs.ollama.com/integrations/hermes",
|
|
22
|
+
ollamaLaunch: "hermes",
|
|
23
|
+
minGenerationTps: 12,
|
|
24
|
+
allowedGrades: ["S", "A"],
|
|
25
|
+
useCases: ["code", "chat", "reasoning"]
|
|
26
|
+
},
|
|
27
|
+
claude: {
|
|
28
|
+
id: "claude",
|
|
29
|
+
docsUrl: "https://docs.ollama.com/integrations/claude-code",
|
|
30
|
+
ollamaLaunch: "claude",
|
|
31
|
+
minGenerationTps: 12,
|
|
32
|
+
allowedGrades: ["S", "A", "B"],
|
|
33
|
+
useCases: ["code", "chat", "reasoning"]
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export {
|
|
38
|
+
INTEGRATIONS
|
|
39
|
+
};
|
|
@@ -49,6 +49,9 @@ var en = {
|
|
|
49
49
|
repoHarnessEnter: "Enter = pick local model from effort Esc = back",
|
|
50
50
|
repoHarnessNoPrompt: "(empty prompt \u2014 effort is still from the graph)",
|
|
51
51
|
repoHarnessModel: "BEST LOCAL MODEL FOR THIS TASK",
|
|
52
|
+
repoHarnessKeys: "1 OpenCode 2 Hermes 3 OpenClaw Enter=OpenCode Esc=back",
|
|
53
|
+
repoHarnessNoTag: "No Ollama tag for this model. N/A \u2014 will not invent a launch command.",
|
|
54
|
+
repoHarnessWroteConfig: "Wrote OpenCode config {file} (local model + pudu-ai MCP tools).",
|
|
52
55
|
repoHelp: `Pudu Agent Lab \u2014 repository search, graph, harness
|
|
53
56
|
|
|
54
57
|
Usage:
|
|
@@ -296,6 +299,9 @@ var es = {
|
|
|
296
299
|
repoHarnessEnter: "Enter = elegir modelo local seg\xFAn esfuerzo Esc = atr\xE1s",
|
|
297
300
|
repoHarnessNoPrompt: "(prompt vac\xEDo \u2014 el esfuerzo sigue saliendo del grafo)",
|
|
298
301
|
repoHarnessModel: "MEJOR MODELO LOCAL PARA ESTA TAREA",
|
|
302
|
+
repoHarnessKeys: "1 OpenCode 2 Hermes 3 OpenClaw Enter=OpenCode Esc=atr\xE1s",
|
|
303
|
+
repoHarnessNoTag: "Este modelo no tiene tag Ollama. N/A \u2014 no se inventa el comando de launch.",
|
|
304
|
+
repoHarnessWroteConfig: "Config OpenCode escrita en {file} (modelo local + tools MCP pudu-ai).",
|
|
299
305
|
repoHelp: `Pudu Agent Lab \u2014 b\xFAsqueda, grafo y harness
|
|
300
306
|
|
|
301
307
|
Uso:
|
|
@@ -513,30 +519,8 @@ function t(id, vars) {
|
|
|
513
519
|
return text;
|
|
514
520
|
}
|
|
515
521
|
|
|
516
|
-
// src/shared/format.ts
|
|
517
|
-
function formatNumber(value, digits = 1) {
|
|
518
|
-
if (value === void 0 || Number.isNaN(value)) return "N/A";
|
|
519
|
-
return value.toFixed(digits);
|
|
520
|
-
}
|
|
521
|
-
function formatPercent(value, digits = 0) {
|
|
522
|
-
if (value === void 0 || Number.isNaN(value)) return "N/A";
|
|
523
|
-
return `${value.toFixed(digits)} %`;
|
|
524
|
-
}
|
|
525
|
-
function formatTokensPerSec(value, digits = 1) {
|
|
526
|
-
if (value === void 0 || Number.isNaN(value)) return "N/A";
|
|
527
|
-
return `${value.toFixed(digits)} t/s`;
|
|
528
|
-
}
|
|
529
|
-
function na(value) {
|
|
530
|
-
if (value === void 0 || value === null || value === "") return "N/A";
|
|
531
|
-
return String(value);
|
|
532
|
-
}
|
|
533
|
-
|
|
534
522
|
export {
|
|
535
523
|
resolveLocale,
|
|
536
524
|
setLocale,
|
|
537
|
-
t
|
|
538
|
-
formatNumber,
|
|
539
|
-
formatPercent,
|
|
540
|
-
formatTokensPerSec,
|
|
541
|
-
na
|
|
525
|
+
t
|
|
542
526
|
};
|
|
@@ -1,9 +1,14 @@
|
|
|
1
|
+
import {
|
|
2
|
+
INTEGRATIONS
|
|
3
|
+
} from "./chunk-47LK5I2X.js";
|
|
1
4
|
import {
|
|
2
5
|
formatNumber,
|
|
3
6
|
formatTokensPerSec,
|
|
4
|
-
na
|
|
7
|
+
na
|
|
8
|
+
} from "./chunk-473MNTMS.js";
|
|
9
|
+
import {
|
|
5
10
|
t
|
|
6
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-4VEDGI4L.js";
|
|
7
12
|
import {
|
|
8
13
|
commandExists,
|
|
9
14
|
runCommand,
|
|
@@ -1053,42 +1058,6 @@ function tasksText(plans, answers) {
|
|
|
1053
1058
|
return [...header, ...blocks].join("\n\n");
|
|
1054
1059
|
}
|
|
1055
1060
|
|
|
1056
|
-
// src/integrations/catalog.ts
|
|
1057
|
-
var INTEGRATIONS = {
|
|
1058
|
-
opencode: {
|
|
1059
|
-
id: "opencode",
|
|
1060
|
-
docsUrl: "https://docs.ollama.com/integrations/opencode",
|
|
1061
|
-
ollamaLaunch: "opencode",
|
|
1062
|
-
minGenerationTps: 12,
|
|
1063
|
-
allowedGrades: ["S", "A", "B"],
|
|
1064
|
-
useCases: ["code", "chat", "reasoning"]
|
|
1065
|
-
},
|
|
1066
|
-
openclaw: {
|
|
1067
|
-
id: "openclaw",
|
|
1068
|
-
docsUrl: "https://docs.ollama.com/integrations/openclaw",
|
|
1069
|
-
ollamaLaunch: "openclaw",
|
|
1070
|
-
minGenerationTps: 12,
|
|
1071
|
-
allowedGrades: ["S", "A", "B"],
|
|
1072
|
-
useCases: ["code", "chat", "reasoning"]
|
|
1073
|
-
},
|
|
1074
|
-
hermes: {
|
|
1075
|
-
id: "hermes",
|
|
1076
|
-
docsUrl: "https://docs.ollama.com/integrations/hermes",
|
|
1077
|
-
ollamaLaunch: "hermes",
|
|
1078
|
-
minGenerationTps: 12,
|
|
1079
|
-
allowedGrades: ["S", "A"],
|
|
1080
|
-
useCases: ["code", "chat", "reasoning"]
|
|
1081
|
-
},
|
|
1082
|
-
claude: {
|
|
1083
|
-
id: "claude",
|
|
1084
|
-
docsUrl: "https://docs.ollama.com/integrations/claude-code",
|
|
1085
|
-
ollamaLaunch: "claude",
|
|
1086
|
-
minGenerationTps: 12,
|
|
1087
|
-
allowedGrades: ["S", "A", "B"],
|
|
1088
|
-
useCases: ["code", "chat", "reasoning"]
|
|
1089
|
-
}
|
|
1090
|
-
};
|
|
1091
|
-
|
|
1092
1061
|
// src/integrations/ollama-tags.ts
|
|
1093
1062
|
var RULES = [
|
|
1094
1063
|
{ test: /qwen\s*3\.5\s*[-:]?\s*8b/i, tag: "qwen3.5:8b" },
|
|
@@ -53,26 +53,9 @@ function parseJsonStdout(stdout) {
|
|
|
53
53
|
return JSON.parse(slice);
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
-
// src/agent-lab/graph.ts
|
|
57
|
-
async function buildRepoGraph(repo, timeoutMs = 6e4) {
|
|
58
|
-
const invoked = await invokePuduAgent({ op: "graph", repo }, timeoutMs);
|
|
59
|
-
if (invoked.timedOut) throw new Error("Python graph engine timed out");
|
|
60
|
-
const parsed = parseJsonStdout(invoked.stdout);
|
|
61
|
-
if (!parsed || parsed.ok === false) {
|
|
62
|
-
throw new Error(parsed.error ?? "graph failed");
|
|
63
|
-
}
|
|
64
|
-
return parsed;
|
|
65
|
-
}
|
|
66
|
-
async function runRepoHarness(repo, task, timeoutMs = 6e4) {
|
|
67
|
-
const invoked = await invokePuduAgent({ op: "harness", repo, task }, timeoutMs);
|
|
68
|
-
if (invoked.timedOut) throw new Error("Python harness engine timed out");
|
|
69
|
-
return parseJsonStdout(invoked.stdout);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
56
|
export {
|
|
57
|
+
pythonRoot,
|
|
73
58
|
findPython,
|
|
74
59
|
invokePuduAgent,
|
|
75
|
-
parseJsonStdout
|
|
76
|
-
buildRepoGraph,
|
|
77
|
-
runRepoHarness
|
|
60
|
+
parseJsonStdout
|
|
78
61
|
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import {
|
|
2
|
+
invokePuduAgent,
|
|
3
|
+
parseJsonStdout
|
|
4
|
+
} from "./chunk-7KPNGK6A.js";
|
|
5
|
+
|
|
6
|
+
// src/agent-lab/graph.ts
|
|
7
|
+
async function buildRepoGraph(repo, timeoutMs = 6e4) {
|
|
8
|
+
const invoked = await invokePuduAgent({ op: "graph", repo }, timeoutMs);
|
|
9
|
+
if (invoked.timedOut) throw new Error("Python graph engine timed out");
|
|
10
|
+
const parsed = parseJsonStdout(invoked.stdout);
|
|
11
|
+
if (!parsed || parsed.ok === false) {
|
|
12
|
+
throw new Error(parsed.error ?? "graph failed");
|
|
13
|
+
}
|
|
14
|
+
return parsed;
|
|
15
|
+
}
|
|
16
|
+
async function runRepoHarness(repo, task, timeoutMs = 6e4) {
|
|
17
|
+
const invoked = await invokePuduAgent({ op: "harness", repo, task }, timeoutMs);
|
|
18
|
+
if (invoked.timedOut) throw new Error("Python harness engine timed out");
|
|
19
|
+
return parseJsonStdout(invoked.stdout);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export {
|
|
23
|
+
buildRepoGraph,
|
|
24
|
+
runRepoHarness
|
|
25
|
+
};
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import {
|
|
2
|
+
INTEGRATIONS
|
|
3
|
+
} from "./chunk-47LK5I2X.js";
|
|
4
|
+
import {
|
|
5
|
+
findPython,
|
|
6
|
+
pythonRoot
|
|
7
|
+
} from "./chunk-7KPNGK6A.js";
|
|
8
|
+
import {
|
|
9
|
+
t
|
|
10
|
+
} from "./chunk-4VEDGI4L.js";
|
|
11
|
+
import {
|
|
12
|
+
commandExists,
|
|
13
|
+
runCommand
|
|
14
|
+
} from "./chunk-SS7OXXQQ.js";
|
|
15
|
+
|
|
16
|
+
// src/integrations/opencode-config.ts
|
|
17
|
+
import { readFile, writeFile, mkdir } from "node:fs/promises";
|
|
18
|
+
import path from "node:path";
|
|
19
|
+
function mergeOpenCodeHarness(existing, ollamaTag, pythonBin, engineRoot) {
|
|
20
|
+
const prev = existing ?? {};
|
|
21
|
+
const provider = prev.provider ?? {};
|
|
22
|
+
const ollama = provider.ollama ?? {};
|
|
23
|
+
const models = ollama.models ?? {};
|
|
24
|
+
const mcp = prev.mcp ?? {};
|
|
25
|
+
return {
|
|
26
|
+
...prev,
|
|
27
|
+
$schema: typeof prev.$schema === "string" ? prev.$schema : "https://opencode.ai/config.json",
|
|
28
|
+
model: `ollama/${ollamaTag}`,
|
|
29
|
+
provider: {
|
|
30
|
+
...provider,
|
|
31
|
+
ollama: {
|
|
32
|
+
...ollama,
|
|
33
|
+
npm: "@ai-sdk/openai-compatible",
|
|
34
|
+
name: typeof ollama.name === "string" ? ollama.name : "Ollama (local)",
|
|
35
|
+
options: {
|
|
36
|
+
...ollama.options ?? {},
|
|
37
|
+
baseURL: "http://127.0.0.1:11434/v1"
|
|
38
|
+
},
|
|
39
|
+
models: {
|
|
40
|
+
...models,
|
|
41
|
+
[ollamaTag]: {
|
|
42
|
+
...models[ollamaTag] ?? {},
|
|
43
|
+
name: ollamaTag
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
mcp: {
|
|
49
|
+
...mcp,
|
|
50
|
+
"pudu-ai": {
|
|
51
|
+
type: "local",
|
|
52
|
+
enabled: true,
|
|
53
|
+
timeout: 12e4,
|
|
54
|
+
command: [pythonBin, "-m", "pudu_agent", "mcp"],
|
|
55
|
+
environment: { PYTHONPATH: engineRoot, PYTHONUNBUFFERED: "1" }
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
async function writeOpenCodeHarnessConfig(input) {
|
|
61
|
+
const file = path.join(input.repo, "opencode.json");
|
|
62
|
+
let existing;
|
|
63
|
+
try {
|
|
64
|
+
const raw = await readFile(file, "utf8");
|
|
65
|
+
const parsed = JSON.parse(raw);
|
|
66
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
67
|
+
existing = parsed;
|
|
68
|
+
}
|
|
69
|
+
} catch {
|
|
70
|
+
existing = void 0;
|
|
71
|
+
}
|
|
72
|
+
const next = mergeOpenCodeHarness(existing, input.ollamaTag, input.pythonBin, pythonRoot());
|
|
73
|
+
await mkdir(input.repo, { recursive: true });
|
|
74
|
+
await writeFile(file, `${JSON.stringify(next, null, 2)}
|
|
75
|
+
`, "utf8");
|
|
76
|
+
return file;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// src/integrations/harness-launch.ts
|
|
80
|
+
async function executeHarnessLaunch(req) {
|
|
81
|
+
const def = INTEGRATIONS[req.tool];
|
|
82
|
+
if (!def) return { ok: false, log: t("launchUnknown") };
|
|
83
|
+
const ollama = await commandExists("ollama");
|
|
84
|
+
if (!ollama) return { ok: false, log: t("launchNeedOllama") };
|
|
85
|
+
const lines = [];
|
|
86
|
+
if (req.tool === "opencode") {
|
|
87
|
+
const python = await findPython();
|
|
88
|
+
if (!python) return { ok: false, log: t("repoPythonMissing") };
|
|
89
|
+
const file = await writeOpenCodeHarnessConfig({
|
|
90
|
+
repo: req.repo,
|
|
91
|
+
ollamaTag: req.ollamaTag,
|
|
92
|
+
pythonBin: python
|
|
93
|
+
});
|
|
94
|
+
lines.push(t("repoHarnessWroteConfig", { file }));
|
|
95
|
+
const bin = await commandExists("opencode");
|
|
96
|
+
if (bin) {
|
|
97
|
+
lines.push(`${bin} (${req.ollamaTag})`);
|
|
98
|
+
const launched2 = await runCommand(bin, [], { cwd: req.repo, timeout: 0, stdio: "inherit" });
|
|
99
|
+
if (launched2.exitCode !== 0) {
|
|
100
|
+
return { ok: false, log: `${lines.join("\n")}
|
|
101
|
+
${launched2.stderr || t("launchExecFail")}` };
|
|
102
|
+
}
|
|
103
|
+
return { ok: true, log: lines.join("\n") };
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
const args = ["launch", def.ollamaLaunch, "--model", req.ollamaTag];
|
|
107
|
+
if (def.id === "openclaw" || def.id === "claude") args.push("--yes");
|
|
108
|
+
lines.push(`ollama ${args.join(" ")}`);
|
|
109
|
+
const launched = await runCommand("ollama", args, { timeout: 0, stdio: "inherit", cwd: req.repo });
|
|
110
|
+
if (launched.exitCode !== 0) {
|
|
111
|
+
return { ok: false, log: `${lines.join("\n")}
|
|
112
|
+
${launched.stderr || t("launchExecFail")}` };
|
|
113
|
+
}
|
|
114
|
+
return { ok: true, log: lines.join("\n") };
|
|
115
|
+
}
|
|
116
|
+
export {
|
|
117
|
+
executeHarnessLaunch
|
|
118
|
+
};
|
package/dist/cli/index.js
CHANGED
|
@@ -32,12 +32,14 @@ import {
|
|
|
32
32
|
runBenchmark,
|
|
33
33
|
runtimesText,
|
|
34
34
|
tasksText
|
|
35
|
-
} from "./chunk-
|
|
35
|
+
} from "./chunk-6BGYI7XY.js";
|
|
36
|
+
import "./chunk-47LK5I2X.js";
|
|
37
|
+
import "./chunk-473MNTMS.js";
|
|
36
38
|
import {
|
|
37
39
|
resolveLocale,
|
|
38
40
|
setLocale,
|
|
39
41
|
t
|
|
40
|
-
} from "./chunk-
|
|
42
|
+
} from "./chunk-4VEDGI4L.js";
|
|
41
43
|
import {
|
|
42
44
|
commandExists,
|
|
43
45
|
killAll,
|
|
@@ -728,8 +730,14 @@ ${modelsText(session)}`)}
|
|
|
728
730
|
);
|
|
729
731
|
return;
|
|
730
732
|
}
|
|
731
|
-
const { renderDashboard } = await import("./render-
|
|
732
|
-
await renderDashboard(session, preset, start);
|
|
733
|
+
const { renderDashboard } = await import("./render-Y4DNH7FW.js");
|
|
734
|
+
const pending = await renderDashboard(session, preset, start);
|
|
735
|
+
if (pending) {
|
|
736
|
+
const { executeHarnessLaunch } = await import("./harness-launch-KEUKZZXH.js");
|
|
737
|
+
const result = await executeHarnessLaunch(pending);
|
|
738
|
+
if (!result.ok) process.stderr.write(`${result.log}
|
|
739
|
+
`);
|
|
740
|
+
}
|
|
733
741
|
}
|
|
734
742
|
async function run(args2) {
|
|
735
743
|
setLocale(resolveLocale(args2.lang));
|
|
@@ -741,7 +749,7 @@ async function run(args2) {
|
|
|
741
749
|
if (args2.verbose) setLogLevel("verbose");
|
|
742
750
|
if (!args2.color) process.env.NO_COLOR = "1";
|
|
743
751
|
if (args2.command === "repo") {
|
|
744
|
-
const { runRepoCommand } = await import("./repo-cli-
|
|
752
|
+
const { runRepoCommand } = await import("./repo-cli-BK76EVBZ.js");
|
|
745
753
|
return runRepoCommand(args2);
|
|
746
754
|
}
|
|
747
755
|
const cleanup = () => {
|
|
@@ -16,16 +16,20 @@ import {
|
|
|
16
16
|
resultText,
|
|
17
17
|
runBenchmark,
|
|
18
18
|
tasksText
|
|
19
|
-
} from "./chunk-
|
|
19
|
+
} from "./chunk-6BGYI7XY.js";
|
|
20
|
+
import "./chunk-47LK5I2X.js";
|
|
20
21
|
import {
|
|
21
22
|
buildRepoGraph
|
|
22
|
-
} from "./chunk-
|
|
23
|
+
} from "./chunk-XEEGLMVT.js";
|
|
23
24
|
import {
|
|
24
25
|
formatNumber,
|
|
25
26
|
formatPercent,
|
|
26
|
-
formatTokensPerSec
|
|
27
|
+
formatTokensPerSec
|
|
28
|
+
} from "./chunk-473MNTMS.js";
|
|
29
|
+
import "./chunk-7KPNGK6A.js";
|
|
30
|
+
import {
|
|
27
31
|
t
|
|
28
|
-
} from "./chunk-
|
|
32
|
+
} from "./chunk-4VEDGI4L.js";
|
|
29
33
|
import "./chunk-SS7OXXQQ.js";
|
|
30
34
|
|
|
31
35
|
// src/tui/render.tsx
|
|
@@ -750,6 +754,7 @@ function pickHarnessModel(session, effort) {
|
|
|
750
754
|
return {
|
|
751
755
|
modelId: null,
|
|
752
756
|
modelName: null,
|
|
757
|
+
ollamaTag: null,
|
|
753
758
|
grade: null,
|
|
754
759
|
measuredTps: null,
|
|
755
760
|
reason: "No installed models. N/A",
|
|
@@ -790,6 +795,7 @@ function pickHarnessModel(session, effort) {
|
|
|
790
795
|
return {
|
|
791
796
|
modelId: best.row.local.id,
|
|
792
797
|
modelName: best.row.local.name,
|
|
798
|
+
ollamaTag: resolveOllamaTag(best.row.local.id, best.row.local.name, best.row.catalog?.id, best.row.catalog?.name) ?? (best.row.local.id.includes(":") ? best.row.local.id : null),
|
|
793
799
|
grade: best.grade,
|
|
794
800
|
measuredTps: best.tps,
|
|
795
801
|
reason: reasons[effort.label],
|
|
@@ -799,7 +805,12 @@ function pickHarnessModel(session, effort) {
|
|
|
799
805
|
|
|
800
806
|
// src/tui/views/Graph.tsx
|
|
801
807
|
import { Fragment as Fragment6, jsx as jsx11, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
802
|
-
|
|
808
|
+
var TOOLS2 = [
|
|
809
|
+
{ key: "1", id: "opencode", label: "OpenCode" },
|
|
810
|
+
{ key: "2", id: "hermes", label: "Hermes" },
|
|
811
|
+
{ key: "3", id: "openclaw", label: "OpenClaw" }
|
|
812
|
+
];
|
|
813
|
+
function GraphView(props) {
|
|
803
814
|
const [phase, setPhase] = useState4("build");
|
|
804
815
|
const [graph, setGraph] = useState4();
|
|
805
816
|
const [error, setError] = useState4("");
|
|
@@ -823,21 +834,38 @@ function GraphView({ session }) {
|
|
|
823
834
|
live = false;
|
|
824
835
|
};
|
|
825
836
|
}, [repo]);
|
|
837
|
+
const pick = useMemo2(() => graph ? pickHarnessModel(props.session, graph.effort) : void 0, [graph, props.session]);
|
|
826
838
|
useInput4((input, key) => {
|
|
827
|
-
if (phase
|
|
828
|
-
|
|
829
|
-
|
|
839
|
+
if (phase === "prompt") {
|
|
840
|
+
if (key.return) {
|
|
841
|
+
setPhase("pick");
|
|
842
|
+
return;
|
|
843
|
+
}
|
|
844
|
+
if (key.backspace || key.delete) {
|
|
845
|
+
setPrompt((value) => value.slice(0, -1));
|
|
846
|
+
return;
|
|
847
|
+
}
|
|
848
|
+
if (input && !key.ctrl && !key.meta && input !== " ") {
|
|
849
|
+
setPrompt((value) => `${value}${input}`.slice(0, 240));
|
|
850
|
+
}
|
|
830
851
|
return;
|
|
831
852
|
}
|
|
832
|
-
if (
|
|
833
|
-
|
|
853
|
+
if (phase !== "pick" || !pick?.modelId) return;
|
|
854
|
+
const tool = TOOLS2.find((item) => item.key === input)?.id ?? (key.return ? "opencode" : void 0);
|
|
855
|
+
if (!tool) return;
|
|
856
|
+
if (!pick.ollamaTag) {
|
|
857
|
+
setError(t("repoHarnessNoTag"));
|
|
858
|
+
setPhase("error");
|
|
834
859
|
return;
|
|
835
860
|
}
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
861
|
+
props.onLaunch({
|
|
862
|
+
tool,
|
|
863
|
+
repo,
|
|
864
|
+
task: prompt,
|
|
865
|
+
modelId: pick.modelId,
|
|
866
|
+
ollamaTag: pick.ollamaTag
|
|
867
|
+
});
|
|
839
868
|
});
|
|
840
|
-
const pick = useMemo2(() => graph ? pickHarnessModel(session, graph.effort) : void 0, [graph, session]);
|
|
841
869
|
if (phase === "build") {
|
|
842
870
|
return /* @__PURE__ */ jsxs11(Panel, { title: t("repoGraphTitle"), color: "magenta", children: [
|
|
843
871
|
/* @__PURE__ */ jsx11(Text11, { children: t("repoGraphBuilding") }),
|
|
@@ -912,11 +940,14 @@ function GraphView({ session }) {
|
|
|
912
940
|
pick?.modelName ? /* @__PURE__ */ jsxs11(Text11, { children: [
|
|
913
941
|
pick.modelName,
|
|
914
942
|
" ",
|
|
943
|
+
pick.ollamaTag ?? "\u2014",
|
|
944
|
+
" ",
|
|
915
945
|
pick.grade ?? "\u2014",
|
|
916
946
|
" ",
|
|
917
947
|
pick.measuredTps != null ? `${pick.measuredTps} t/s` : t("notTested")
|
|
918
948
|
] }) : /* @__PURE__ */ jsx11(Text11, { color: "yellow", children: pick?.reason ?? "N/A" }),
|
|
919
|
-
/* @__PURE__ */ jsx11(Text11, { dimColor: true, children: pick?.reason })
|
|
949
|
+
/* @__PURE__ */ jsx11(Text11, { dimColor: true, children: pick?.reason }),
|
|
950
|
+
pick?.ollamaTag ? /* @__PURE__ */ jsx11(Text11, { color: "cyan", children: t("repoHarnessKeys") }) : /* @__PURE__ */ jsx11(Text11, { color: "yellow", children: t("repoHarnessNoTag") })
|
|
920
951
|
] })
|
|
921
952
|
] });
|
|
922
953
|
}
|
|
@@ -981,7 +1012,16 @@ function App(props) {
|
|
|
981
1012
|
screen === "compare" && /* @__PURE__ */ jsx12(CompareView, { session: props.session }),
|
|
982
1013
|
screen === "history" && /* @__PURE__ */ jsx12(HistoryView, { session: props.session }),
|
|
983
1014
|
screen === "tasks" && /* @__PURE__ */ jsx12(TasksView, { session: props.session }),
|
|
984
|
-
screen === "graph" && /* @__PURE__ */ jsx12(
|
|
1015
|
+
screen === "graph" && /* @__PURE__ */ jsx12(
|
|
1016
|
+
GraphView,
|
|
1017
|
+
{
|
|
1018
|
+
session: props.session,
|
|
1019
|
+
onLaunch: (req) => {
|
|
1020
|
+
props.onLaunch?.(req);
|
|
1021
|
+
exit();
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
1024
|
+
),
|
|
985
1025
|
screen === "benchmark" && /* @__PURE__ */ jsx12(
|
|
986
1026
|
BenchmarkView,
|
|
987
1027
|
{
|
|
@@ -999,11 +1039,26 @@ function App(props) {
|
|
|
999
1039
|
// src/tui/render.tsx
|
|
1000
1040
|
import { jsx as jsx13 } from "react/jsx-runtime";
|
|
1001
1041
|
async function renderDashboard(session, preset, start) {
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1042
|
+
let pending;
|
|
1043
|
+
const instance = render(
|
|
1044
|
+
/* @__PURE__ */ jsx13(
|
|
1045
|
+
App,
|
|
1046
|
+
{
|
|
1047
|
+
session,
|
|
1048
|
+
preset,
|
|
1049
|
+
start,
|
|
1050
|
+
onLaunch: (req) => {
|
|
1051
|
+
pending = req;
|
|
1052
|
+
}
|
|
1053
|
+
}
|
|
1054
|
+
),
|
|
1055
|
+
{
|
|
1056
|
+
exitOnCtrlC: true,
|
|
1057
|
+
patchConsole: true
|
|
1058
|
+
}
|
|
1059
|
+
);
|
|
1006
1060
|
await instance.waitUntilExit();
|
|
1061
|
+
return pending;
|
|
1007
1062
|
}
|
|
1008
1063
|
export {
|
|
1009
1064
|
renderDashboard
|
|
@@ -1,14 +1,18 @@
|
|
|
1
1
|
import {
|
|
2
2
|
buildRepoGraph,
|
|
3
|
+
runRepoHarness
|
|
4
|
+
} from "./chunk-XEEGLMVT.js";
|
|
5
|
+
import {
|
|
6
|
+
na
|
|
7
|
+
} from "./chunk-473MNTMS.js";
|
|
8
|
+
import {
|
|
3
9
|
findPython,
|
|
4
10
|
invokePuduAgent,
|
|
5
|
-
parseJsonStdout
|
|
6
|
-
|
|
7
|
-
} from "./chunk-LL5FQT7I.js";
|
|
11
|
+
parseJsonStdout
|
|
12
|
+
} from "./chunk-7KPNGK6A.js";
|
|
8
13
|
import {
|
|
9
|
-
na,
|
|
10
14
|
t
|
|
11
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-4VEDGI4L.js";
|
|
12
16
|
import "./chunk-SS7OXXQQ.js";
|
|
13
17
|
|
|
14
18
|
// src/agent-lab/repo-cli.ts
|
package/package.json
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
|
-
|
|
1
|
+
import sys
|
|
2
2
|
|
|
3
3
|
if __name__ == "__main__":
|
|
4
|
+
if len(sys.argv) > 1 and sys.argv[1] == "mcp":
|
|
5
|
+
from pudu_agent.mcp_server import main as mcp_main
|
|
6
|
+
|
|
7
|
+
raise SystemExit(mcp_main())
|
|
8
|
+
from pudu_agent.search import main
|
|
9
|
+
|
|
4
10
|
raise SystemExit(main())
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import sys
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from pudu_agent.graph import build_graph
|
|
9
|
+
from pudu_agent.search import handle_search
|
|
10
|
+
|
|
11
|
+
PROTOCOL = "2024-11-05"
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _read() -> dict[str, Any] | None:
|
|
15
|
+
headers: dict[str, str] = {}
|
|
16
|
+
while True:
|
|
17
|
+
line = sys.stdin.buffer.readline()
|
|
18
|
+
if not line:
|
|
19
|
+
return None
|
|
20
|
+
if line in (b"\r\n", b"\n"):
|
|
21
|
+
break
|
|
22
|
+
decoded = line.decode("utf-8", errors="replace")
|
|
23
|
+
if ":" not in decoded:
|
|
24
|
+
continue
|
|
25
|
+
key, value = decoded.split(":", 1)
|
|
26
|
+
headers[key.strip().lower()] = value.strip()
|
|
27
|
+
length = int(headers.get("content-length") or "0")
|
|
28
|
+
if length <= 0:
|
|
29
|
+
return None
|
|
30
|
+
body = sys.stdin.buffer.read(length)
|
|
31
|
+
return json.loads(body.decode("utf-8"))
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _write(payload: dict[str, Any]) -> None:
|
|
35
|
+
raw = json.dumps(payload, ensure_ascii=False).encode("utf-8")
|
|
36
|
+
sys.stdout.buffer.write(f"Content-Length: {len(raw)}\r\n\r\n".encode("ascii"))
|
|
37
|
+
sys.stdout.buffer.write(raw)
|
|
38
|
+
sys.stdout.buffer.flush()
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _result(req_id: Any, result: Any) -> dict[str, Any]:
|
|
42
|
+
return {"jsonrpc": "2.0", "id": req_id, "result": result}
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _error(req_id: Any, message: str) -> dict[str, Any]:
|
|
46
|
+
return {"jsonrpc": "2.0", "id": req_id, "error": {"code": -32000, "message": message}}
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _tools() -> list[dict[str, Any]]:
|
|
50
|
+
return [
|
|
51
|
+
{
|
|
52
|
+
"name": "pudu_repo_search",
|
|
53
|
+
"description": "Deterministic repo search via rg/ast-grep/graph. No LLM. Never invents hits.",
|
|
54
|
+
"inputSchema": {
|
|
55
|
+
"type": "object",
|
|
56
|
+
"properties": {
|
|
57
|
+
"repo": {"type": "string"},
|
|
58
|
+
"query": {"type": "string"},
|
|
59
|
+
"intent": {"type": "string"},
|
|
60
|
+
"limit": {"type": "integer"},
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
"name": "pudu_repo_graph",
|
|
66
|
+
"description": "Build a Python AST implementation graph. Edges are EXTRACTED or RESOLVED.",
|
|
67
|
+
"inputSchema": {
|
|
68
|
+
"type": "object",
|
|
69
|
+
"properties": {"repo": {"type": "string"}},
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
]
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def _call(name: str, args: dict[str, Any]) -> str:
|
|
76
|
+
repo = args.get("repo") or "."
|
|
77
|
+
if name == "pudu_repo_search":
|
|
78
|
+
result = handle_search(
|
|
79
|
+
{
|
|
80
|
+
"op": "search",
|
|
81
|
+
"repo": repo,
|
|
82
|
+
"query": args.get("query"),
|
|
83
|
+
"intent": args.get("intent"),
|
|
84
|
+
"limit": args.get("limit") or 50,
|
|
85
|
+
}
|
|
86
|
+
)
|
|
87
|
+
return json.dumps(result, ensure_ascii=False)
|
|
88
|
+
if name == "pudu_repo_graph":
|
|
89
|
+
graph = build_graph(Path(str(repo)).expanduser().resolve(), persist=True)
|
|
90
|
+
slim = {
|
|
91
|
+
"ok": graph.get("ok"),
|
|
92
|
+
"repo": graph.get("repo"),
|
|
93
|
+
"backend": graph.get("backend"),
|
|
94
|
+
"metrics": graph.get("metrics"),
|
|
95
|
+
"effort": graph.get("effort"),
|
|
96
|
+
"writtenTo": graph.get("writtenTo"),
|
|
97
|
+
"nodeCount": len(graph.get("nodes") or []),
|
|
98
|
+
"edgeCount": len(graph.get("edges") or []),
|
|
99
|
+
}
|
|
100
|
+
return json.dumps(slim, ensure_ascii=False)
|
|
101
|
+
raise ValueError(f"unknown tool: {name}")
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def main() -> int:
|
|
105
|
+
while True:
|
|
106
|
+
req = _read()
|
|
107
|
+
if req is None:
|
|
108
|
+
return 0
|
|
109
|
+
method = req.get("method")
|
|
110
|
+
req_id = req.get("id")
|
|
111
|
+
if method == "initialize":
|
|
112
|
+
_write(
|
|
113
|
+
_result(
|
|
114
|
+
req_id,
|
|
115
|
+
{
|
|
116
|
+
"protocolVersion": PROTOCOL,
|
|
117
|
+
"capabilities": {"tools": {}},
|
|
118
|
+
"serverInfo": {"name": "pudu-ai", "version": "0.2.23"},
|
|
119
|
+
},
|
|
120
|
+
)
|
|
121
|
+
)
|
|
122
|
+
continue
|
|
123
|
+
if method == "notifications/initialized" or method == "initialized":
|
|
124
|
+
continue
|
|
125
|
+
if method == "tools/list":
|
|
126
|
+
_write(_result(req_id, {"tools": _tools()}))
|
|
127
|
+
continue
|
|
128
|
+
if method == "tools/call":
|
|
129
|
+
params = req.get("params") or {}
|
|
130
|
+
name = params.get("name")
|
|
131
|
+
arguments = params.get("arguments") or {}
|
|
132
|
+
try:
|
|
133
|
+
text = _call(str(name), arguments if isinstance(arguments, dict) else {})
|
|
134
|
+
_write(_result(req_id, {"content": [{"type": "text", "text": text}]}))
|
|
135
|
+
except Exception as exc:
|
|
136
|
+
_write(_error(req_id, str(exc)))
|
|
137
|
+
continue
|
|
138
|
+
if req_id is not None:
|
|
139
|
+
_write(_error(req_id, f"unknown method: {method}"))
|
|
140
|
+
return 0
|