pudu-ai 0.2.21 → 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/README.md +2 -0
- package/dist/cli/chunk-473MNTMS.js +24 -0
- package/dist/cli/chunk-47LK5I2X.js +39 -0
- package/dist/cli/{chunk-QGEXKMQ5.js → chunk-4VEDGI4L.js} +56 -31
- package/dist/cli/{chunk-6C5EPKRI.js → chunk-6BGYI7XY.js} +7 -38
- package/dist/cli/chunk-7KPNGK6A.js +61 -0
- package/dist/cli/chunk-XEEGLMVT.js +25 -0
- package/dist/cli/harness-launch-KEUKZZXH.js +118 -0
- package/dist/cli/index.js +17 -7
- package/dist/cli/{render-3UBPZNF4.js → render-Y4DNH7FW.js} +279 -26
- package/dist/cli/{repo-cli-4GSG5MD3.js → repo-cli-BK76EVBZ.js} +90 -71
- 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/graph.py +398 -0
- package/python/pudu_agent/mcp_server.py +140 -0
- package/python/pudu_agent/search.py +61 -15
|
@@ -16,21 +16,28 @@ import {
|
|
|
16
16
|
resultText,
|
|
17
17
|
runBenchmark,
|
|
18
18
|
tasksText
|
|
19
|
-
} from "./chunk-
|
|
19
|
+
} from "./chunk-6BGYI7XY.js";
|
|
20
|
+
import "./chunk-47LK5I2X.js";
|
|
21
|
+
import {
|
|
22
|
+
buildRepoGraph
|
|
23
|
+
} from "./chunk-XEEGLMVT.js";
|
|
20
24
|
import {
|
|
21
25
|
formatNumber,
|
|
22
26
|
formatPercent,
|
|
23
|
-
formatTokensPerSec
|
|
27
|
+
formatTokensPerSec
|
|
28
|
+
} from "./chunk-473MNTMS.js";
|
|
29
|
+
import "./chunk-7KPNGK6A.js";
|
|
30
|
+
import {
|
|
24
31
|
t
|
|
25
|
-
} from "./chunk-
|
|
32
|
+
} from "./chunk-4VEDGI4L.js";
|
|
26
33
|
import "./chunk-SS7OXXQQ.js";
|
|
27
34
|
|
|
28
35
|
// src/tui/render.tsx
|
|
29
36
|
import { render } from "ink";
|
|
30
37
|
|
|
31
38
|
// src/tui/App.tsx
|
|
32
|
-
import { useApp as useApp2, useInput as
|
|
33
|
-
import { useMemo as
|
|
39
|
+
import { useApp as useApp2, useInput as useInput5 } from "ink";
|
|
40
|
+
import { useMemo as useMemo3, useRef as useRef2, useState as useState5 } from "react";
|
|
34
41
|
|
|
35
42
|
// src/tui/views/Dashboard.tsx
|
|
36
43
|
import { Box as Box2, Text as Text2 } from "ink";
|
|
@@ -84,6 +91,7 @@ var NAV = [
|
|
|
84
91
|
{ key: "M", label: "Models", color: "cyan" },
|
|
85
92
|
{ key: "R", label: "Rec", color: "magenta" },
|
|
86
93
|
{ key: "T", label: "Tasks", color: "blue" },
|
|
94
|
+
{ key: "G", label: "Graph", color: "magenta" },
|
|
87
95
|
{ key: "H", label: "HW", color: "white" },
|
|
88
96
|
{ key: "C", label: "Cmp", color: "green" },
|
|
89
97
|
{ key: "L", label: "Log", color: "cyan" },
|
|
@@ -729,6 +737,221 @@ function TasksView({ session }) {
|
|
|
729
737
|
] });
|
|
730
738
|
}
|
|
731
739
|
|
|
740
|
+
// src/tui/views/Graph.tsx
|
|
741
|
+
import { Box as Box10, Text as Text11, useInput as useInput4 } from "ink";
|
|
742
|
+
import { useEffect as useEffect2, useMemo as useMemo2, useState as useState4 } from "react";
|
|
743
|
+
|
|
744
|
+
// src/agent-lab/harness.ts
|
|
745
|
+
var GRADE_RANK = { S: 0, A: 1, B: 2, C: 3, D: 4, F: 5 };
|
|
746
|
+
function isCodeish(useCase) {
|
|
747
|
+
const cases = useCase ?? ["chat", "code"];
|
|
748
|
+
return cases.some((item) => item.includes("code") || item.includes("chat"));
|
|
749
|
+
}
|
|
750
|
+
function pickHarnessModel(session, effort) {
|
|
751
|
+
const rows = session.rows.filter((row) => isCodeish(row.catalog?.useCase));
|
|
752
|
+
const pool = rows.length ? rows : session.rows;
|
|
753
|
+
if (!pool.length) {
|
|
754
|
+
return {
|
|
755
|
+
modelId: null,
|
|
756
|
+
modelName: null,
|
|
757
|
+
ollamaTag: null,
|
|
758
|
+
grade: null,
|
|
759
|
+
measuredTps: null,
|
|
760
|
+
reason: "No installed models. N/A",
|
|
761
|
+
origin: null
|
|
762
|
+
};
|
|
763
|
+
}
|
|
764
|
+
const scored = pool.map((row) => ({
|
|
765
|
+
row,
|
|
766
|
+
grade: row.compatibility?.grade ?? "C",
|
|
767
|
+
tps: row.lastBenchmark?.benchmark.generationTokensPerSecond ?? null,
|
|
768
|
+
params: row.catalog?.paramsBillions ?? null
|
|
769
|
+
}));
|
|
770
|
+
scored.sort((a, b) => {
|
|
771
|
+
if (effort.label === "LOW") {
|
|
772
|
+
if (a.tps != null && b.tps != null && a.tps !== b.tps) return b.tps - a.tps;
|
|
773
|
+
if (a.tps != null && b.tps == null) return -1;
|
|
774
|
+
if (a.tps == null && b.tps != null) return 1;
|
|
775
|
+
return (GRADE_RANK[a.grade] ?? 9) - (GRADE_RANK[b.grade] ?? 9);
|
|
776
|
+
}
|
|
777
|
+
if (effort.label === "HIGH" || effort.label === "VERY_HIGH") {
|
|
778
|
+
if (a.params != null && b.params != null && a.params !== b.params) return b.params - a.params;
|
|
779
|
+
if (a.params != null && b.params == null) return -1;
|
|
780
|
+
if (a.params == null && b.params != null) return 1;
|
|
781
|
+
return (GRADE_RANK[a.grade] ?? 9) - (GRADE_RANK[b.grade] ?? 9);
|
|
782
|
+
}
|
|
783
|
+
const grade = (GRADE_RANK[a.grade] ?? 9) - (GRADE_RANK[b.grade] ?? 9);
|
|
784
|
+
if (grade !== 0) return grade;
|
|
785
|
+
if (a.tps != null && b.tps != null) return b.tps - a.tps;
|
|
786
|
+
return 0;
|
|
787
|
+
});
|
|
788
|
+
const best = scored[0];
|
|
789
|
+
const reasons = {
|
|
790
|
+
LOW: "LOW effort: prefer fastest measured local coding model; else best grade.",
|
|
791
|
+
MEDIUM: "MEDIUM effort: prefer best hardware grade, then measured t/s.",
|
|
792
|
+
HIGH: "HIGH effort: prefer larger catalog params when known; else best grade.",
|
|
793
|
+
VERY_HIGH: "VERY_HIGH effort: prefer larger catalog params when known; else best grade."
|
|
794
|
+
};
|
|
795
|
+
return {
|
|
796
|
+
modelId: best.row.local.id,
|
|
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),
|
|
799
|
+
grade: best.grade,
|
|
800
|
+
measuredTps: best.tps,
|
|
801
|
+
reason: reasons[effort.label],
|
|
802
|
+
origin: "DERIVED"
|
|
803
|
+
};
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
// src/tui/views/Graph.tsx
|
|
807
|
+
import { Fragment as Fragment6, jsx as jsx11, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
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) {
|
|
814
|
+
const [phase, setPhase] = useState4("build");
|
|
815
|
+
const [graph, setGraph] = useState4();
|
|
816
|
+
const [error, setError] = useState4("");
|
|
817
|
+
const [prompt, setPrompt] = useState4("");
|
|
818
|
+
const repo = process.cwd();
|
|
819
|
+
useEffect2(() => {
|
|
820
|
+
let live = true;
|
|
821
|
+
void (async () => {
|
|
822
|
+
try {
|
|
823
|
+
const result = await buildRepoGraph(repo);
|
|
824
|
+
if (!live) return;
|
|
825
|
+
setGraph(result);
|
|
826
|
+
setPhase("prompt");
|
|
827
|
+
} catch (err) {
|
|
828
|
+
if (!live) return;
|
|
829
|
+
setError(err instanceof Error ? err.message : String(err));
|
|
830
|
+
setPhase("error");
|
|
831
|
+
}
|
|
832
|
+
})();
|
|
833
|
+
return () => {
|
|
834
|
+
live = false;
|
|
835
|
+
};
|
|
836
|
+
}, [repo]);
|
|
837
|
+
const pick = useMemo2(() => graph ? pickHarnessModel(props.session, graph.effort) : void 0, [graph, props.session]);
|
|
838
|
+
useInput4((input, key) => {
|
|
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
|
+
}
|
|
851
|
+
return;
|
|
852
|
+
}
|
|
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");
|
|
859
|
+
return;
|
|
860
|
+
}
|
|
861
|
+
props.onLaunch({
|
|
862
|
+
tool,
|
|
863
|
+
repo,
|
|
864
|
+
task: prompt,
|
|
865
|
+
modelId: pick.modelId,
|
|
866
|
+
ollamaTag: pick.ollamaTag
|
|
867
|
+
});
|
|
868
|
+
});
|
|
869
|
+
if (phase === "build") {
|
|
870
|
+
return /* @__PURE__ */ jsxs11(Panel, { title: t("repoGraphTitle"), color: "magenta", children: [
|
|
871
|
+
/* @__PURE__ */ jsx11(Text11, { children: t("repoGraphBuilding") }),
|
|
872
|
+
/* @__PURE__ */ jsx11(Text11, { dimColor: true, children: repo })
|
|
873
|
+
] });
|
|
874
|
+
}
|
|
875
|
+
if (phase === "error") {
|
|
876
|
+
return /* @__PURE__ */ jsx11(Panel, { title: t("repoGraphTitle"), color: "magenta", children: /* @__PURE__ */ jsx11(Text11, { color: "red", children: error }) });
|
|
877
|
+
}
|
|
878
|
+
if (!graph) return /* @__PURE__ */ jsx11(Text11, { children: " " });
|
|
879
|
+
const degree = /* @__PURE__ */ new Map();
|
|
880
|
+
for (const edge of graph.edges) {
|
|
881
|
+
degree.set(edge.source, (degree.get(edge.source) ?? 0) + 1);
|
|
882
|
+
}
|
|
883
|
+
const top = [...graph.nodes].filter((node) => node.type === "file" || node.type === "function" || node.type === "test").sort((a, b) => (degree.get(b.id) ?? 0) - (degree.get(a.id) ?? 0)).slice(0, 8);
|
|
884
|
+
return /* @__PURE__ */ jsxs11(Fragment6, { children: [
|
|
885
|
+
/* @__PURE__ */ jsxs11(Panel, { title: t("repoGraphTitle"), color: "magenta", children: [
|
|
886
|
+
/* @__PURE__ */ jsx11(Text11, { dimColor: true, children: fit(graph.repo, 80) }),
|
|
887
|
+
/* @__PURE__ */ jsxs11(Text11, { children: [
|
|
888
|
+
t("repoGraphFiles"),
|
|
889
|
+
" ",
|
|
890
|
+
graph.metrics.fileCount,
|
|
891
|
+
" ",
|
|
892
|
+
t("repoGraphNodes"),
|
|
893
|
+
" ",
|
|
894
|
+
graph.metrics.nodeCount,
|
|
895
|
+
" ",
|
|
896
|
+
t("repoGraphEdges"),
|
|
897
|
+
" ",
|
|
898
|
+
graph.metrics.edgeCount,
|
|
899
|
+
" (",
|
|
900
|
+
graph.metrics.origin,
|
|
901
|
+
")"
|
|
902
|
+
] }),
|
|
903
|
+
top.map((node) => /* @__PURE__ */ jsxs11(Box10, { children: [
|
|
904
|
+
/* @__PURE__ */ jsx11(Text11, { color: "cyan", children: fit(node.file ?? node.name, 36) }),
|
|
905
|
+
/* @__PURE__ */ jsxs11(Text11, { children: [
|
|
906
|
+
" ",
|
|
907
|
+
fit(node.name, 18),
|
|
908
|
+
" "
|
|
909
|
+
] }),
|
|
910
|
+
/* @__PURE__ */ jsx11(Text11, { dimColor: true, children: degree.get(node.id) ?? 0 })
|
|
911
|
+
] }, node.id)),
|
|
912
|
+
!top.length ? /* @__PURE__ */ jsx11(Text11, { dimColor: true, children: t("repoGraphEmpty") }) : null
|
|
913
|
+
] }),
|
|
914
|
+
/* @__PURE__ */ jsxs11(Panel, { title: t("repoEffortTitle"), color: "yellow", children: [
|
|
915
|
+
/* @__PURE__ */ jsxs11(Text11, { children: [
|
|
916
|
+
graph.effort.label,
|
|
917
|
+
" ",
|
|
918
|
+
graph.effort.score0to100,
|
|
919
|
+
"/100 (",
|
|
920
|
+
graph.effort.origin,
|
|
921
|
+
")"
|
|
922
|
+
] }),
|
|
923
|
+
/* @__PURE__ */ jsxs11(Text11, { dimColor: true, children: [
|
|
924
|
+
t("repoTokens"),
|
|
925
|
+
" ",
|
|
926
|
+
graph.metrics.tokenEstimate ?? "N/A",
|
|
927
|
+
" (",
|
|
928
|
+
graph.metrics.tokenOrigin ?? "N/A",
|
|
929
|
+
")"
|
|
930
|
+
] })
|
|
931
|
+
] }),
|
|
932
|
+
phase === "prompt" ? /* @__PURE__ */ jsxs11(Panel, { title: t("repoHarnessPrompt"), color: "blue", children: [
|
|
933
|
+
/* @__PURE__ */ jsxs11(Text11, { children: [
|
|
934
|
+
prompt || t("repoHarnessPromptHint"),
|
|
935
|
+
/* @__PURE__ */ jsx11(Text11, { color: "cyan", children: "\u258D" })
|
|
936
|
+
] }),
|
|
937
|
+
/* @__PURE__ */ jsx11(Text11, { dimColor: true, children: t("repoHarnessEnter") })
|
|
938
|
+
] }) : /* @__PURE__ */ jsxs11(Panel, { title: t("repoHarnessModel"), color: "green", children: [
|
|
939
|
+
/* @__PURE__ */ jsx11(Text11, { dimColor: true, children: prompt || t("repoHarnessNoPrompt") }),
|
|
940
|
+
pick?.modelName ? /* @__PURE__ */ jsxs11(Text11, { children: [
|
|
941
|
+
pick.modelName,
|
|
942
|
+
" ",
|
|
943
|
+
pick.ollamaTag ?? "\u2014",
|
|
944
|
+
" ",
|
|
945
|
+
pick.grade ?? "\u2014",
|
|
946
|
+
" ",
|
|
947
|
+
pick.measuredTps != null ? `${pick.measuredTps} t/s` : t("notTested")
|
|
948
|
+
] }) : /* @__PURE__ */ jsx11(Text11, { color: "yellow", children: pick?.reason ?? "N/A" }),
|
|
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") })
|
|
951
|
+
] })
|
|
952
|
+
] });
|
|
953
|
+
}
|
|
954
|
+
|
|
732
955
|
// src/tui/keys.ts
|
|
733
956
|
function globalNav(letter) {
|
|
734
957
|
if (letter === "b") return { type: "screen", screen: "benchmark" };
|
|
@@ -739,11 +962,16 @@ function globalNav(letter) {
|
|
|
739
962
|
if (letter === "l") return { type: "screen", screen: "history" };
|
|
740
963
|
if (letter === "t") return { type: "screen", screen: "tasks" };
|
|
741
964
|
if (letter === "s") return { type: "screen", screen: "setup" };
|
|
965
|
+
if (letter === "g") return { type: "screen", screen: "graph" };
|
|
742
966
|
return void 0;
|
|
743
967
|
}
|
|
744
968
|
function handleAppKey(screen, input, key) {
|
|
745
969
|
const letter = input.toLowerCase();
|
|
746
970
|
if (screen === "benchmark") return { type: "ignore" };
|
|
971
|
+
if (screen === "graph") {
|
|
972
|
+
if (key.escape) return { type: "home" };
|
|
973
|
+
return { type: "ignore" };
|
|
974
|
+
}
|
|
747
975
|
if (letter === "q") return { type: "quit" };
|
|
748
976
|
if (key.escape) return screen === "home" ? { type: "quit" } : { type: "home" };
|
|
749
977
|
if (screen === "setup") {
|
|
@@ -757,34 +985,44 @@ function handleAppKey(screen, input, key) {
|
|
|
757
985
|
}
|
|
758
986
|
|
|
759
987
|
// src/tui/App.tsx
|
|
760
|
-
import { jsx as
|
|
988
|
+
import { jsx as jsx12, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
761
989
|
function App(props) {
|
|
762
990
|
const { exit } = useApp2();
|
|
763
|
-
const [screen, setScreen] =
|
|
991
|
+
const [screen, setScreen] = useState5(props.start ?? "home");
|
|
764
992
|
const screenRef = useRef2(screen);
|
|
765
993
|
screenRef.current = screen;
|
|
766
|
-
const [selected, setSelected] =
|
|
767
|
-
const benchable =
|
|
994
|
+
const [selected, setSelected] = useState5(0);
|
|
995
|
+
const benchable = useMemo3(
|
|
768
996
|
() => props.session.models.filter((m) => Boolean(m.artifactPath)),
|
|
769
997
|
[props.session.models]
|
|
770
998
|
);
|
|
771
|
-
|
|
999
|
+
useInput5((input, key) => {
|
|
772
1000
|
const result = handleAppKey(screenRef.current, input, key);
|
|
773
1001
|
if (result.type === "quit") exit();
|
|
774
1002
|
if (result.type === "home") setScreen("home");
|
|
775
1003
|
if (result.type === "screen") setScreen(result.screen);
|
|
776
1004
|
});
|
|
777
|
-
return /* @__PURE__ */
|
|
778
|
-
screen !== "benchmark" && /* @__PURE__ */
|
|
779
|
-
screen === "home" && /* @__PURE__ */
|
|
780
|
-
screen === "models" && /* @__PURE__ */
|
|
781
|
-
screen === "hardware" && /* @__PURE__ */
|
|
782
|
-
screen === "recommend" && /* @__PURE__ */
|
|
783
|
-
screen === "setup" && /* @__PURE__ */
|
|
784
|
-
screen === "compare" && /* @__PURE__ */
|
|
785
|
-
screen === "history" && /* @__PURE__ */
|
|
786
|
-
screen === "tasks" && /* @__PURE__ */
|
|
787
|
-
screen === "
|
|
1005
|
+
return /* @__PURE__ */ jsxs12(Frame, { children: [
|
|
1006
|
+
screen !== "benchmark" && /* @__PURE__ */ jsx12(Header, {}),
|
|
1007
|
+
screen === "home" && /* @__PURE__ */ jsx12(Dashboard, { session: props.session }),
|
|
1008
|
+
screen === "models" && /* @__PURE__ */ jsx12(ModelsView, { session: props.session }),
|
|
1009
|
+
screen === "hardware" && /* @__PURE__ */ jsx12(HardwareView, { session: props.session }),
|
|
1010
|
+
screen === "recommend" && /* @__PURE__ */ jsx12(RecommendView, { session: props.session }),
|
|
1011
|
+
screen === "setup" && /* @__PURE__ */ jsx12(SetupView, { session: props.session }),
|
|
1012
|
+
screen === "compare" && /* @__PURE__ */ jsx12(CompareView, { session: props.session }),
|
|
1013
|
+
screen === "history" && /* @__PURE__ */ jsx12(HistoryView, { session: props.session }),
|
|
1014
|
+
screen === "tasks" && /* @__PURE__ */ jsx12(TasksView, { session: props.session }),
|
|
1015
|
+
screen === "graph" && /* @__PURE__ */ jsx12(
|
|
1016
|
+
GraphView,
|
|
1017
|
+
{
|
|
1018
|
+
session: props.session,
|
|
1019
|
+
onLaunch: (req) => {
|
|
1020
|
+
props.onLaunch?.(req);
|
|
1021
|
+
exit();
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
1024
|
+
),
|
|
1025
|
+
screen === "benchmark" && /* @__PURE__ */ jsx12(
|
|
788
1026
|
BenchmarkView,
|
|
789
1027
|
{
|
|
790
1028
|
session: props.session,
|
|
@@ -799,13 +1037,28 @@ function App(props) {
|
|
|
799
1037
|
}
|
|
800
1038
|
|
|
801
1039
|
// src/tui/render.tsx
|
|
802
|
-
import { jsx as
|
|
1040
|
+
import { jsx as jsx13 } from "react/jsx-runtime";
|
|
803
1041
|
async function renderDashboard(session, preset, start) {
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
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
|
+
);
|
|
808
1060
|
await instance.waitUntilExit();
|
|
1061
|
+
return pending;
|
|
809
1062
|
}
|
|
810
1063
|
export {
|
|
811
1064
|
renderDashboard
|
|
@@ -1,64 +1,22 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
} from "./chunk-
|
|
2
|
+
buildRepoGraph,
|
|
3
|
+
runRepoHarness
|
|
4
|
+
} from "./chunk-XEEGLMVT.js";
|
|
5
|
+
import {
|
|
6
|
+
na
|
|
7
|
+
} from "./chunk-473MNTMS.js";
|
|
8
|
+
import {
|
|
9
|
+
findPython,
|
|
10
|
+
invokePuduAgent,
|
|
11
|
+
parseJsonStdout
|
|
12
|
+
} from "./chunk-7KPNGK6A.js";
|
|
5
13
|
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
14
|
+
t
|
|
15
|
+
} from "./chunk-4VEDGI4L.js";
|
|
16
|
+
import "./chunk-SS7OXXQQ.js";
|
|
9
17
|
|
|
10
18
|
// src/agent-lab/repo-cli.ts
|
|
11
|
-
import path2 from "node:path";
|
|
12
|
-
|
|
13
|
-
// src/agent-lab/python-bridge.ts
|
|
14
|
-
import { existsSync } from "node:fs";
|
|
15
19
|
import path from "node:path";
|
|
16
|
-
import { fileURLToPath } from "node:url";
|
|
17
|
-
function hasEngine(dir) {
|
|
18
|
-
return existsSync(path.join(dir, "pudu_agent", "__main__.py"));
|
|
19
|
-
}
|
|
20
|
-
function pythonRoot() {
|
|
21
|
-
const starts = [path.dirname(fileURLToPath(import.meta.url)), process.cwd()];
|
|
22
|
-
for (const start of starts) {
|
|
23
|
-
let dir = start;
|
|
24
|
-
for (let i = 0; i < 8; i++) {
|
|
25
|
-
const candidate = path.join(dir, "python");
|
|
26
|
-
if (hasEngine(candidate)) return candidate;
|
|
27
|
-
const parent = path.dirname(dir);
|
|
28
|
-
if (parent === dir) break;
|
|
29
|
-
dir = parent;
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
return path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", "..", "python");
|
|
33
|
-
}
|
|
34
|
-
async function findPython() {
|
|
35
|
-
return await commandExists("python3") ?? await commandExists("python");
|
|
36
|
-
}
|
|
37
|
-
async function invokePuduAgent(request, timeout = 3e4) {
|
|
38
|
-
const python = await findPython();
|
|
39
|
-
if (!python) {
|
|
40
|
-
throw new Error("python3 not found on PATH");
|
|
41
|
-
}
|
|
42
|
-
const root = pythonRoot();
|
|
43
|
-
const payload = `${JSON.stringify(request)}
|
|
44
|
-
`;
|
|
45
|
-
const result = await runCommand(python, ["-m", "pudu_agent"], {
|
|
46
|
-
cwd: root,
|
|
47
|
-
env: { ...process.env, PYTHONPATH: root, PYTHONUNBUFFERED: "1" },
|
|
48
|
-
input: payload,
|
|
49
|
-
timeout
|
|
50
|
-
});
|
|
51
|
-
return { ...result, python };
|
|
52
|
-
}
|
|
53
|
-
function parseJsonStdout(stdout) {
|
|
54
|
-
const text = stdout.trim();
|
|
55
|
-
if (!text) {
|
|
56
|
-
throw new Error("Python engine returned empty stdout");
|
|
57
|
-
}
|
|
58
|
-
const start = text.indexOf("{");
|
|
59
|
-
const slice = start >= 0 ? text.slice(start) : text;
|
|
60
|
-
return JSON.parse(slice);
|
|
61
|
-
}
|
|
62
20
|
|
|
63
21
|
// src/agent-lab/schema.ts
|
|
64
22
|
import { z } from "zod";
|
|
@@ -83,7 +41,7 @@ var searchMatchSchema = z.object({
|
|
|
83
41
|
endLine: z.number().nullable(),
|
|
84
42
|
endColumn: z.number().nullable(),
|
|
85
43
|
text: z.string(),
|
|
86
|
-
strategy: z.enum(["rg", "ast-grep"]),
|
|
44
|
+
strategy: z.enum(["rg", "ast-grep", "graph"]),
|
|
87
45
|
language: z.string().nullable(),
|
|
88
46
|
metavariables: z.record(z.string())
|
|
89
47
|
});
|
|
@@ -205,16 +163,41 @@ async function runRepoCommand(args) {
|
|
|
205
163
|
print(t("repoHelp"), args.json);
|
|
206
164
|
return 0;
|
|
207
165
|
}
|
|
166
|
+
const python = await findPython();
|
|
167
|
+
if (!python) {
|
|
168
|
+
process.stderr.write(`${t("repoPythonMissing")}
|
|
169
|
+
`);
|
|
170
|
+
return 1;
|
|
171
|
+
}
|
|
172
|
+
const repo = path.resolve(args.repo ?? process.cwd());
|
|
173
|
+
if (sub === "graph") {
|
|
174
|
+
try {
|
|
175
|
+
const graph = await buildRepoGraph(repo);
|
|
176
|
+
print(args.json ? graph : formatGraphText(graph), args.json);
|
|
177
|
+
return graph.ok ? 0 : 1;
|
|
178
|
+
} catch (error) {
|
|
179
|
+
return fail(error, args.json);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
if (sub === "harness") {
|
|
183
|
+
const task = args.task ?? args.positional.slice(1).join(" ").trim() ?? "";
|
|
184
|
+
if (!task) {
|
|
185
|
+
process.stderr.write(`${t("repoNeedTask")}
|
|
186
|
+
`);
|
|
187
|
+
return 1;
|
|
188
|
+
}
|
|
189
|
+
try {
|
|
190
|
+
const harness = await runRepoHarness(repo, task);
|
|
191
|
+
print(args.json ? harness : formatGraphText(harness.graph, task), args.json);
|
|
192
|
+
return harness.ok ? 0 : 1;
|
|
193
|
+
} catch (error) {
|
|
194
|
+
return fail(error, args.json);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
208
197
|
if (sub !== "search") {
|
|
209
198
|
process.stderr.write(`${t("repoUnknownSubcommand", { sub })}
|
|
210
199
|
`);
|
|
211
200
|
process.stderr.write(`${t("repoHelp")}
|
|
212
|
-
`);
|
|
213
|
-
return 1;
|
|
214
|
-
}
|
|
215
|
-
const python = await findPython();
|
|
216
|
-
if (!python) {
|
|
217
|
-
process.stderr.write(`${t("repoPythonMissing")}
|
|
218
201
|
`);
|
|
219
202
|
return 1;
|
|
220
203
|
}
|
|
@@ -226,7 +209,6 @@ async function runRepoCommand(args) {
|
|
|
226
209
|
return 1;
|
|
227
210
|
}
|
|
228
211
|
const intent = asIntent(args.intent);
|
|
229
|
-
const repo = path2.resolve(args.repo ?? process.cwd());
|
|
230
212
|
try {
|
|
231
213
|
const result = await searchRepo({
|
|
232
214
|
repo,
|
|
@@ -239,17 +221,54 @@ async function runRepoCommand(args) {
|
|
|
239
221
|
print(args.json ? result : formatSearchText(result), args.json);
|
|
240
222
|
return result.ok ? 0 : 1;
|
|
241
223
|
} catch (error) {
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
224
|
+
return fail(error, args.json);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
function fail(error, json) {
|
|
228
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
229
|
+
if (json) print({ ok: false, error: message }, true);
|
|
230
|
+
else process.stderr.write(`${message}
|
|
247
231
|
`);
|
|
248
|
-
|
|
249
|
-
|
|
232
|
+
return 1;
|
|
233
|
+
}
|
|
234
|
+
function formatGraphText(graph, task) {
|
|
235
|
+
const e = graph.effort;
|
|
236
|
+
const m = graph.metrics;
|
|
237
|
+
const lines = [
|
|
238
|
+
t("repoGraphTitle"),
|
|
239
|
+
"",
|
|
240
|
+
t("aboutRepoGraph"),
|
|
241
|
+
"",
|
|
242
|
+
`Repo ${graph.repo}`,
|
|
243
|
+
`Backend ${graph.backend}`,
|
|
244
|
+
`Files ${m.fileCount} (${m.origin})`,
|
|
245
|
+
`Nodes ${m.nodeCount} (${m.origin})`,
|
|
246
|
+
`Edges ${m.edgeCount} (${m.origin})`,
|
|
247
|
+
`Tokens ${m.tokenEstimate ?? "N/A"} (${m.tokenOrigin ?? "N/A"})`,
|
|
248
|
+
`Effort ${e.label} ${e.score0to100}/100 (${e.origin})`
|
|
249
|
+
];
|
|
250
|
+
if (task) lines.push(`Task ${task}`);
|
|
251
|
+
if (graph.writtenTo) lines.push(`Wrote ${graph.writtenTo}`);
|
|
252
|
+
for (const err of graph.errors) {
|
|
253
|
+
lines.push(`Error ${err.tool}: ${err.message} (${err.origin})`);
|
|
254
|
+
}
|
|
255
|
+
lines.push("");
|
|
256
|
+
const degree = /* @__PURE__ */ new Map();
|
|
257
|
+
for (const edge of graph.edges) {
|
|
258
|
+
degree.set(edge.source, (degree.get(edge.source) ?? 0) + 1);
|
|
259
|
+
degree.set(edge.target, (degree.get(edge.target) ?? 0) + 1);
|
|
250
260
|
}
|
|
261
|
+
const top = [...graph.nodes].filter((n) => n.type === "file" || n.type === "function" || n.type === "test").sort((a, b) => (degree.get(b.id) ?? 0) - (degree.get(a.id) ?? 0)).slice(0, 12);
|
|
262
|
+
for (const node of top) {
|
|
263
|
+
const related = graph.edges.filter((edge) => edge.source === node.id).slice(0, 3).map((edge) => `${edge.type}\u2192${edge.symbol ?? edge.target} [${edge.confidence}]`);
|
|
264
|
+
lines.push(`${node.file ?? node.name} ${node.name} (${degree.get(node.id) ?? 0})`);
|
|
265
|
+
for (const rel of related) lines.push(` ${rel}`);
|
|
266
|
+
}
|
|
267
|
+
if (!top.length) lines.push(t("repoGraphEmpty"));
|
|
268
|
+
return lines.join("\n");
|
|
251
269
|
}
|
|
252
270
|
export {
|
|
271
|
+
formatGraphText,
|
|
253
272
|
formatSearchText,
|
|
254
273
|
runRepoCommand
|
|
255
274
|
};
|
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
|